public class Fermentable extends Ingredient implements Parcelable {

  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  // Name - Inherited
  // Version - Inherited
  private String type;                           // Grain, Extract, Adjunct
  private double yield;                          // Dry yeild / raw yield
  private double color;                          // Color in Lovibond (SRM)

  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private boolean addAfterBoil;                  // True if added after boil
  private double maxInBatch;                     // Max reccomended in this batch

  // Custom Fields ==================================================
  // ================================================================
  private String description;                     // Description of fermentable

  // Static values =================================================
  // ===============================================================
  public static final String TYPE_GRAIN = "Grain";
  public static final String TYPE_EXTRACT = "Extract";
  public static final String TYPE_ADJUNCT = "Adjunct";
  public static final String TYPE_SUGAR = "Sugar";

  public Fermentable(String name) {
    super(name);
    this.type = TYPE_GRAIN;
    this.amount = 0;
    this.yield = 1;
    this.color = 0;
    this.addAfterBoil = false;
    this.setMaxInBatch(0);
    this.description = "No description provided.";
  }

  public Fermentable(Parcel p) {
    super(p);
    type = p.readString();
    yield = p.readDouble();
    color = p.readDouble();
    addAfterBoil = p.readInt() > 0;
    maxInBatch = p.readDouble();
    description = p.readString();
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    super.writeToParcel(p, flags);
    p.writeString(type);                           // Grain, Extract, Adjunct
    p.writeDouble(yield);                          // Dry yeild / raw yield
    p.writeDouble(color);                          // Color in Lovibond (SRM)
    p.writeInt(addAfterBoil ? 1 : 0);              // True if added after boil
    p.writeDouble(maxInBatch);                     // Max reccomended in this batch
    p.writeString(description);                    // Description of fermentable
  }

  public static final Parcelable.Creator<Fermentable> CREATOR =
          new Parcelable.Creator<Fermentable>() {
            @Override
            public Fermentable createFromParcel(Parcel p) {
              return new Fermentable(p);
            }

            @Override
            public Fermentable[] newArray(int size) {
              return null;
            }
          };

  @Override
  public String getType() {
    return Ingredient.FERMENTABLE;
  }

  public double getLovibondColor() {
    color = (float) Math.round(color * 10) / 10;
    return color;
  }

  public void setLovibondColor(double color) {
    color = (float) Math.round(color * 10) / 10;
    this.color = color;
  }

  public double getGravity() {
    return yieldToGravity(yield);
  }

  public void setGravity(double gravity) {
    this.yield = gravityToYield(gravity);
  }

  public float getPpg() {
    return (float) (this.yield * 46) / 100;
  }

  public String getFermentableType() {
    return type;
  }

  public void setFermentableType(String type) {
    if (! Constants.FERMENTABLE_TYPES.contains(type)) {
      Log.e("Fermentable", "Setting invalid fermentable type: " + type);
    }
    this.type = type;
  }

  @Override
  public double getDisplayAmount() {
    if (getDisplayUnits().equals(Units.POUNDS)) {
      return Units.kilosToPounds(this.amount);
    }
    else {
      return this.amount;
    }
  }

  @Override
  public double getBeerXmlStandardAmount() {
    return this.amount;
  }

  @Override
  public void setDisplayAmount(double amt) {
    if (getDisplayUnits().equals(Units.POUNDS)) {
      this.amount = Units.poundsToKilos(amt);
    }
    else {
      this.amount = amt;
    }
  }

  @Override
  public void setBeerXmlStandardAmount(double amt) {
    this.amount = amt;
  }

  @Override
  public int hashCode() {
    int hc = this.getName().hashCode();
    hc += this.getLovibondColor();
    hc |= (int) this.getGravity() * 1234;
    hc += (int) this.getBeerXmlStandardAmount();
    hc |= this.getType().hashCode();
    return hc;
  }

  @Override
  public String getDisplayUnits() {
    return Units.getFermentableUnits();
  }

  @Override
  public void setDisplayUnits(String s) {
  }

  @Override
  public String getBeerXmlStandardUnits() {
    return Units.KILOGRAMS;
  }

  /**
   * Evaluates equality of this Fermentable and the given Object.
   *
   * @param o
   * @return true if this Fermentable equals Object o, false otherwise.
   */
  @Override
  public boolean equals(Object o) {
    if (o instanceof Fermentable) {
      // Compare to other.
      return this.compareTo((Fermentable) o) == 0;
    }

    // Not a fermentable, and so is not equal.
    return false;
  }

  @Override
  public int getTime() {
    return time;
  }

  @Override
  public void setTime(int time) {
    this.time = time;
  }

  /**
   * @return the yield
   */
  public double getYield() {
    return yield;
  }

  /**
   * @param yield
   *         the yield to set
   */
  public void setYield(double yield) {
    this.yield = yield;
  }

  /**
   * @return the addAfterBoil
   */
  public boolean isAddAfterBoil() {
    return addAfterBoil;
  }

  /**
   * @param addAfterBoil
   *         the addAfterBoil to set
   */
  public void setAddAfterBoil(boolean addAfterBoil) {
    this.addAfterBoil = addAfterBoil;
  }

  @Override
  public void setShortDescription(String description) {
    this.description = description;
  }

  @Override
  public String getShortDescription() {
    return this.description;
  }

  /**
   * @return the maxInBatch
   */
  public double getMaxInBatch() {
    return maxInBatch;
  }

  @Override
  public String getUse() {
    if (this.getFermentableType().equals(TYPE_EXTRACT) ||
            this.getFermentableType().equals(TYPE_SUGAR)) {
      // Sugars and Extracts should be boiled.
      return Ingredient.USE_BOIL;
    }
    else {
      // Everything else should be mashed. Grains, Adjuncts.
      return Ingredient.USE_MASH;
    }
  }

  /**
   * @param maxInBatch
   *         the maxInBatch to set
   */
  public void setMaxInBatch(double maxInBatch) {
    this.maxInBatch = maxInBatch;
  }

  /**
   * Turns given gravity into a yield
   *
   * @param gravity
   * @return
   */
  public double gravityToYield(double gravity) {
    double yield = 0;

    yield = gravity - 1;
    yield = yield * 1000;
    yield = yield * 100 / 46;

    return yield;
  }

  /**
   *
   * @return The gravity points contributed by this fermentable.
   */
  public double gravityPoints() {
    return this.getPpg() * Units.kilosToPounds(this.amount);
  }

  /**
   * turns given yield into a gravity
   *
   * @param yield
   * @return
   */
  public double yieldToGravity(double yield) {
    return 1 + (((yield * 46) / 100) / 1000);
  }

  /**
   * Compares the given Ingredient to this Fermentable and returns and indicator of (in)equality.
   *
   * @param other
   *         The Ingredient with which to compare this Fermentable
   * @return 0 if argument is equal to this, < 0 if argument is greater than this, > 0 if argument
   * is less than this
   */
  @Override
  public int compareTo(Ingredient other) {
    // If not the same type of Ingredient, sort based on Ingredient type.
    int typeCompare = this.getType().compareTo(other.getType());
    if (typeCompare != 0) {
      return typeCompare;
    }
    Fermentable f = (Fermentable) other;

    // If they are both Fermentables, sort based on amount.
    int amountCompare = Double.compare(f.getBeerXmlStandardAmount(), this.getBeerXmlStandardAmount());
    if (amountCompare != 0) {
      return amountCompare;
    }

    // Sort based on name.
    int nameCompare = this.getName().compareTo(f.getName());
    if (nameCompare != 0) {
      return nameCompare;
    }

    // Sort based on color.
    int colorCompare = Double.compare(this.getLovibondColor(), f.getLovibondColor());
    if (colorCompare != 0) {
      return colorCompare;
    }

    // Sort based on gravity.
    int gravCompare = Double.compare(this.getGravity(), f.getGravity());
    if (gravCompare != 0) {
      return gravCompare;
    }

    // If all the above are equal, they are the same.
    return 0;
  }
} public class Hop extends Ingredient {

  // Beer XML 1.0 Required Fields ===================================
  // ================================================================

  // Name - Inherited
  // Version - Inherited
  private double alpha;                       // Alpha in %
  private String use;                         // Boil, Dry Hop, etc

  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private String type;                        // Bittering, Armoa, Both
  private String form;                        // Pellet, plug, whole
  private String origin;                      // Place of origin
  private ArrayList<String> substitutes;      // Substitute options

  // Custom Fields ==================================================
  // ================================================================
  private String description;                 // Short description of flavor / use

  // Static values =================================================
  // ===============================================================
  public static final String FORM_PELLET = "Pellet";
  public static final String FORM_WHOLE = "Whole";
  public static final String FORM_PLUG = "Plug";

  // Hop types
  public static final String TYPE_BITTERING = "Bittering";
  public static final String TYPE_AROMA = "Aromatic";
  public static final String TYPE_BOTH = "Bittering / Aromatic";

  public Hop(String name) {
    super(name);
    this.amount = 0;
    this.alpha = 0;
    this.use = USE_BOIL;
    this.type = TYPE_BOTH;
    this.form = FORM_PELLET;
    this.origin = "";
    this.substitutes = new ArrayList<String>(); // TODO Get this from somewhere
    this.description = "No description";
  }

  public Hop(Parcel p) {
    super(p);
    this.substitutes = new ArrayList<String>();
    alpha = p.readDouble();
    use = p.readString();

    type = p.readString();
    form = p.readString();
    origin = p.readString();
    p.readStringList(this.substitutes);
    description = p.readString();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    super.writeToParcel(p, flags);

    // Required
    p.writeDouble(alpha);
    p.writeString(use);

    // Optional
    p.writeString(type);
    p.writeString(form);
    p.writeString(origin);
    p.writeStringList(substitutes);
    p.writeString(description);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  public static final Parcelable.Creator<Hop> CREATOR =
          new Parcelable.Creator<Hop>() {
            @Override
            public Hop createFromParcel(Parcel p) {
              return new Hop(p);
            }

            @Override
            public Hop[] newArray(int size) {
              return null;
            }
          };

  @Override
  public String getType() {
    return Ingredient.HOP;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public double getAlphaAcidContent() {
    return alpha;
  }

  public void setAlphaAcidContent(double alpha) {
    this.alpha = alpha;
  }

  @Override
  public String getShortDescription() {
    return this.description;
  }

  @Override
  public double getDisplayAmount() {
    if (getDisplayUnits().equals(Units.OUNCES)) {
      return Units.kilosToOunces(this.amount);
    }
    else {
      return Units.kilosToGrams(this.amount);
    }
  }

  public int getDisplayTime() {
    if (this.getUse().equals(Hop.USE_DRY_HOP)) {
      return (int) Units.minutesToDays(this.time);
    }
    else {
      return this.time;
    }
  }

  public void setDisplayTime(int time) {
    if (this.getUse().equals(Hop.USE_DRY_HOP)) {
      this.time = (int) Units.daysToMinutes(time);
    }
    else {
      this.time = time;
    }
  }

  @Override
  public double getBeerXmlStandardAmount() {
    return this.amount;
  }

  @Override
  public void setDisplayAmount(double amt) {
    if (getDisplayUnits().equals(Units.OUNCES)) {
      this.amount = Units.ouncesToKilos(amt);
    }
    else {
      this.amount = Units.gramsToKilos(amt);
    }
  }

  @Override
  public void setBeerXmlStandardAmount(double amt) {
    this.amount = amt;
  }

  public String getForm() {
    return form;
  }

  public void setForm(String form) {
    this.form = form;
  }

  @Override
  public String getDisplayUnits() {
    return Units.getHopUnits();
  }

  public void setDisplayUnits(String s) {
  }

  @Override
  public String getBeerXmlStandardUnits() {
    return Units.KILOGRAMS;
  }

  @Override
  public int hashCode() {
    int hc = this.getName().hashCode();
    hc = hc ^ (int) (getAlphaAcidContent() * 1234);
    hc += this.getTime();
    hc += this.getUse().hashCode();
    hc += this.getType().hashCode();
    return hc;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof Hop) {
      return this.compareTo((Hop) o) == 0;
    }
    return false;
  }

  /**
   * @return the use
   */
  public String getUse() {
    return use;
  }

  /**
   * @param use
   *         the use to set
   */
  public void setUse(String use) {
    this.use = use;
  }

  /**
   * @return the time
   */
  public int getBeerXmlStandardTime() {
    return time;
  }

  @Override
  public int getTime() {
    return this.getDisplayTime();
  }

  @Override
  public void setTime(int time) {
    setDisplayTime(time);
  }

  /**
   * @return the origin
   */
  public String getOrigin() {
    return origin;
  }

  /**
   * @param origin
   *         the origin to set
   */
  public void setOrigin(String origin) {
    this.origin = origin;
  }

  /**
   * @return the substitutes
   */
  public ArrayList<String> getSubstitutes() {
    return substitutes;
  }

  /**
   * @param substitutes
   *         the substitutes to set
   */
  public void setSubstitutes(ArrayList<String> substitutes) {
    this.substitutes = substitutes;
  }

  /**
   * @return the hopType
   */
  public String getHopType() {
    return type;
  }

  /**
   * @param type
   *         the hopType to set
   */
  public void setHopType(String type) {
    this.type = type;
  }

  /**
   * @param time
   *         the time to set
   */
  public void setBeerXmlStandardTime(int time) {
    this.time = time;
  }

  @Override
  public void setShortDescription(String description) {
    this.description = description;
  }

  @Override
  public int compareTo(Ingredient other) {
    // If not the same type, sort based on type.
    int typeCompare = this.getType().compareTo(other.getType());
    if (typeCompare != 0) {
      return typeCompare;
    }
    Hop h = (Hop) other;

    // If they are not the same use, sort based on use.
    if (! this.getUse().equals(other.getUse())) {
      return this.getUse().compareTo(other.getUse());
    }

    // Sort based on time.
    int timeCompare = Double.compare(h.getTime(), this.getTime());
    if (timeCompare != 0) {
      return timeCompare;
    }

    // Sort based on name.
    int nameCompare = this.getName().compareTo(h.getName());
    if (nameCompare != 0) {
      return nameCompare;
    }

    // Sort based on alpha acid.
    int alphaCompare = Double.compare(this.getAlphaAcidContent(), h.getAlphaAcidContent());
    if (alphaCompare != 0) {
      return alphaCompare;
    }

    // If all else is the same, they are equal.
    return 0;
  }
} public abstract class Ingredient implements Parcelable {

  // Beer XML 1.0 Required Fields (To be inherited) =================
  // ================================================================
  private String name;                    // Ingredient name
  private int version;                   // bXML Version being used
  public double amount;                  // Amount in beerXML standard units
  public int time;                       // Time ingredient is used - units vary based on use

  // Custom Fields ==================================================
  // ================================================================
  private long id;                        // Lookup ID for database
  private long ownerId;                   // ID of recipe that contains this
  private double inventory;               // Amount in inventory (standard units)
  private long databaseId;                // Which virtual database to store this in

  // Static values =================================================
  // ===============================================================
  public static final String FERMENTABLE = "Fermentable";
  public static final String HOP = "Hop";
  public static final String YEAST = "Yeast";
  public static final String MISC = "Misc";
  public static final String WATER = "Water";
  public static final String PLACEHOLDER = "Placeholder";

  // Ingredient uses
  public static final String USE_BOIL = "Boil";
  public static final String USE_MASH = "Mash";
  public static final String USE_PRIMARY = "Primary";
  public static final String USE_SECONDARY = "Secondary";
  public static final String USE_BOTTLING = "Bottling";
  public static final String USE_DRY_HOP = "Dry Hop";
  public static final String USE_FIRST_WORT = "First Wort";
  public static final String USE_AROMA = "Aroma";
  public static final String USE_OTHER = "other";

  // Public constructors
  public Ingredient(String name) {
    this.name = name;
    this.id = Constants.INVALID_ID;
    this.ownerId = Constants.INVALID_ID;
    this.databaseId = Constants.DATABASE_USER_RECIPES;
    this.amount = 0;
    this.time = 0;
    this.inventory = 0;
  }

  public Ingredient(Parcel p) {
    name = p.readString();
    version = p.readInt();
    amount = p.readDouble();
    id = p.readLong();
    ownerId = p.readLong();
    inventory = p.readDouble();
    databaseId = p.readLong();
    time = p.readInt();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    // Should be called by all sub-classes
    p.writeString(name);
    p.writeInt(version);
    p.writeDouble(amount);
    p.writeLong(id);
    p.writeLong(ownerId);
    p.writeDouble(inventory);
    p.writeLong(databaseId);
    p.writeInt(time);
  }

  public static final Parcelable.Creator<Ingredient> CREATOR =
          new Parcelable.Creator<Ingredient>() {
            @Override
            public Ingredient createFromParcel(Parcel p) {
              Ingredient i = null;
              try {
                i = new Hop(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a hop");
              }
              try {
                i = new Misc(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a Misc");
              }
              try {
                i = new Fermentable(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a Fermentable");
              }
              try {
                i = new Yeast(p);
              } catch (Exception e) {
                Log.d("Ingredient", "Not a Yeast");
              }

              return i;
            }

            @Override
            public Ingredient[] newArray(int size) {
              return new Ingredient[]{};
            }
          };

  // Abstract methods of Ingredient
  public abstract String getType();

  public abstract String getShortDescription();

  public abstract void setShortDescription(String description);

  // Get beerXML 1.0 Units
  public abstract String getBeerXmlStandardUnits();

  // Get display units
  public abstract String getDisplayUnits();

  // Set display units
  public abstract void setDisplayUnits(String s);

  // Returns display amount
  public abstract double getDisplayAmount();

  // Takes display amount
  public abstract void setDisplayAmount(double amt);

  // Returns amount in units specified by beerXML 1.0
  public abstract double getBeerXmlStandardAmount();

  // Takes amount in beerXML 1.0 units
  public abstract void setBeerXmlStandardAmount(double amt);

  public abstract int hashCode();

  public abstract boolean equals(Object o);

  public abstract int compareTo(Ingredient other);

  public abstract void setTime(int time);

  public abstract int getTime();

  @Override
  public String toString() {
    return name;
  }

  public String getUse() {
    return Ingredient.USE_OTHER;
  }

  public long getDatabaseId() {
    return this.databaseId;
  }

  public void setDatabaseId(long i) {
    this.databaseId = i;
  }

  public double getBeerXmlStandardInventory() {
    return this.inventory;
  }

  public void setBeerXmlStandardInventory(double d) {
    this.inventory = d;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public long getOwnerId() {
    return ownerId;
  }

  public void setOwnerId(long ownerId) {
    this.ownerId = ownerId;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public int getVersion() {
    return version;
  }

  public void setVersion(int version) {
    this.version = version;
  }
} public class Misc extends Ingredient {

  // Beer XML 1.0 Required Fields (To be inherited) =================
  // ================================================================
  private String miscType;
  private String use;
  private boolean amountIsWeight;
  private String useFor;
  private String notes;

  // Custom Fields ==================================================
  // ================================================================
  private String displayUnits;

  // Static values =================================================
  // ===============================================================
  public static String TYPE_SPICE = "Spice";
  public static String TYPE_FINING = "Fining";
  public static String TYPE_WATER_AGENT = "Water Agent";
  public static String TYPE_HERB = "Herb";
  public static String TYPE_FLAVOR = "Flavor";
  public static String TYPE_OTHER = "Other";

  public Misc(String name) {
    super(name);
    setDisplayUnits("");
    setAmountIsWeight(false);
    setBeerXmlStandardAmount(0);
    setShortDescription("");
    this.amount = 0;
    setUse("");
    setVersion(1);
    setTime(0);
    setMiscType(TYPE_OTHER);
  }

  public Misc(Parcel p) {
    super(p);

    // Required
    this.miscType = p.readString();
    this.use = p.readString();
    this.amount = p.readDouble();
    this.amountIsWeight = p.readInt() > 0 ? true : false;
    this.useFor = p.readString();
    this.notes = p.readString();

    // Optional
    this.displayUnits = p.readString();
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    super.writeToParcel(p, flags);

    // Required
    p.writeString(this.miscType);
    p.writeString(this.use);
    p.writeDouble(this.amount);
    p.writeInt(amountIsWeight ? 1 : 0);
    p.writeString(this.useFor);
    p.writeString(this.notes);

    // Optional
    p.writeString(this.displayUnits);
  }

  public static final Parcelable.Creator<Misc> CREATOR =
          new Parcelable.Creator<Misc>() {
            @Override
            public Misc createFromParcel(Parcel p) {
              return new Misc(p);
            }

            @Override
            public Misc[] newArray(int size) {
              return null;
            }
          };

  @Override
  public String toString() {
    return this.getName();
  }

  public void setMiscType(String type) {
    this.miscType = type;
  }

  public String getMiscType() {
    if (! miscType.isEmpty() || this.miscType == null) {
      return this.miscType;
    }
    else {
      return Misc.TYPE_OTHER;
    }
  }

  public String getArrayAdapterDescription() {
    String s = this.getUse() + ", ";
    if (getTime() > 0) {
      s += this.getTime() + " " + this.getTimeUnits() + ", ";
    }
    s += getUseFor();
    return s;
  }

  public void setUse(String u) {
    this.use = u;
  }

  public String getUse() {
    return this.use;
  }

  public void setAmountIsWeight(boolean b) {
    this.amountIsWeight = b;
  }

  public boolean amountIsWeight() {
    return this.amountIsWeight;
  }

  public void setUseFor(String s) {
    this.useFor = s;
  }

  public String getUseFor() {
    return this.useFor;
  }

  @Override
  public String getType() {
    return Ingredient.MISC;
  }

  @Override
  public String getShortDescription() {
    return notes;
  }

  @Override
  public double getDisplayAmount() {

    String unit = this.getDisplayUnits();

    if (unit.equalsIgnoreCase(Units.GALLONS)) {
      return Units.litersToGallons(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.GRAMS)) {
      return Units.kilosToGrams(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.MILLIGRAMS)) {
      return Units.kilosToMilligrams(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.KILOGRAMS)) {
      return this.amount;
    }
    else if (unit.equalsIgnoreCase(Units.LITERS)) {
      return this.amount;
    }
    else if (unit.equalsIgnoreCase(Units.MILLILITERS)) {
      return Units.litersToMillis(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.TEASPOONS)) {
      return Units.litersToTeaspoons(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.TABLESPOONS)) {
      return Units.litersToTablespoons(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.OUNCES)) {
      if (this.amountIsWeight()) {
        return Units.kilosToOunces(this.amount);
      }
      else {
        return Units.litersToOunces(this.amount);
      }
    }
    else if (unit.equalsIgnoreCase(Units.POUNDS)) {
      return Units.kilosToPounds(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.QUARTS)) {
      return Units.litersToQuarts(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.PINTS)) {
      return Units.litersToPints(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.CUP) || unit.equalsIgnoreCase(Units.CUPS)) {
      return Units.litersToCups(this.amount);
    }
    else if (unit.equalsIgnoreCase(Units.ITEMS)) {
      return this.amount;
    }
    else if (unit.equalsIgnoreCase(Units.PACKAGES)) {
      return this.amount;
    }
    else {
      Log.e("Misc", "Failed to get display amount - bad units? " + unit);
      return 0; // Should show that we couldn't compute
    }
  }

  @Override
  public double getBeerXmlStandardAmount() {
    return this.amount;
  }

  @Override
  public void setDisplayAmount(double amt) {
    // Not implemented - must use other method.
    // Get punished! (Can't throw exceptions here, but to make debugging
    // easier lets cause a problem!)
    int x = (Integer) null;
    x = x / 2;
  }

  public void setDisplayAmount(double amt, String unit) {
    Log.d(getName() + "::setDisplayAmount", "Setting amt to: " + amt + " " + unit);
    if (unit.equalsIgnoreCase(Units.GALLONS)) {
      this.amount = Units.gallonsToLiters(amt);
    }
    else if (unit.equalsIgnoreCase(Units.GRAMS)) {
      this.amount = Units.gramsToKilos(amt);
    }
    else if (unit.equalsIgnoreCase(Units.MILLIGRAMS)) {
      this.amount = Units.milligramsToKilos(amt);
    }
    else if (unit.equalsIgnoreCase(Units.KILOGRAMS)) {
      this.amount = amt;
    }
    else if (unit.equalsIgnoreCase(Units.LITERS)) {
      this.amount = amt;
      this.amountIsWeight = false;
    }
    else if (unit.equalsIgnoreCase(Units.MILLILITERS)) {
      this.amount = Units.millisToLiters(amt);
      this.amountIsWeight = false;
    }
    else if (unit.equalsIgnoreCase(Units.TEASPOONS)) {
      this.amount = Units.teaspoonsToLiters(amt);
    }
    else if (unit.equalsIgnoreCase(Units.TABLESPOONS)) {
      this.amount = Units.tablespoonsToLiters(amt);
    }
    else if (unit.equalsIgnoreCase(Units.PINTS)) {
      this.amount = Units.pintsToLiters(amt);
    }
    else if (unit.equalsIgnoreCase(Units.OUNCES)) {
      if (this.amountIsWeight()) {
        this.amount = Units.ouncesToKilos(amt);
      }
      else {
        this.amount = Units.ouncesToLiters(amt);
      }
    }
    else if (unit.equalsIgnoreCase(Units.POUNDS)) {
      this.amount = Units.poundsToKilos(amt);
    }
    else if (unit.equalsIgnoreCase(Units.CUP) || unit.equalsIgnoreCase(Units.CUPS)) {
      this.amount = Units.cupsToLiters(amt);
    }
    else if (unit.equalsIgnoreCase(Units.ITEMS)) {
      this.amount = amt;
    }
    else if (unit.equalsIgnoreCase(Units.PACKAGES)) {
      this.amount = amt;
    }
    else {
      this.amount = - 1.0; // Should show that we couldn't compute
    }
  }

  @Override
  public void setBeerXmlStandardAmount(double amt) {
    this.amount = amt;
  }

  // This method is used when we want the value of the stored displayUnits
  // Not for use to determine the display units.
  public String getUnits() {
    return this.displayUnits;
  }

  @Override
  public String getDisplayUnits() {
    // If not, call the appropriate method based on which unit system the user has chosen
    if (Units.getUnitSystem().equals(Units.IMPERIAL)) {
      return getDisplayUnitsImperial();
    }
    else {
      return getDisplayUnitsMetric();
    }
  }

  // Called when preferred units are metric
  private String getDisplayUnitsMetric() {
    // First check if we have units specified
    if (! this.displayUnits.equals("")) {
      return Units.getMetricEquivalent(this.displayUnits, this.amountIsWeight);
    }

    if (this.amountIsWeight()) {
      if (this.amount > 1) {
        return Units.KILOGRAMS;
      }
      else {
        return Units.GRAMS;
      }
    }
    else if (this.amount > .5) {
      return Units.LITERS;
    }
    else {
      return Units.MILLILITERS;
    }
  }

  // Called when preferred units are imperial
  private String getDisplayUnitsImperial() {
    // First check if we have units specified
    if (! this.displayUnits.equals("")) {
      return this.displayUnits;
    }

    if (this.amountIsWeight()) {
      if (this.amount > .113)// .25lbs
      {
        return Units.POUNDS;
      }
      else {
        return Units.OUNCES;
      }
    }
    else if (this.amount > .946) // .25gal
    {
      return Units.GALLONS;
    }
    else {
      return Units.OUNCES;
    }
  }

  @Override
  public String getBeerXmlStandardUnits() {
    if (this.amountIsWeight()) {
      return Units.KILOGRAMS;
    }
    else {
      return Units.LITERS;
    }
  }

  @Override
  public int hashCode() {
    int hc = this.getName().hashCode();
    hc = hc ^ this.use.hashCode();
    hc = hc ^ this.miscType.hashCode();
    hc = hc + this.displayUnits.hashCode();
    hc = hc + this.getTime();
    return hc;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof Misc) {
      return this.compareTo((Misc) o) == 0;
    }
    return false;
  }

  @Override
  public int compareTo(Ingredient other) {
    // If not the same type of Ingredient, sort based on Ingredient type.
    int typeCompare = this.getType().compareTo(other.getType());
    if (typeCompare != 0) {
      return typeCompare;
    }
    Misc m = (Misc) other;

    // If they are both Misc, sort based on use.
    int useCompare = this.getUse().compareTo(m.getUse());
    if (useCompare != 0) {
      return useCompare;
    }

    // Sort based on time.
    int timeCompare = Double.compare(m.getTime(), this.getTime());
    if (timeCompare != 0) {
      return timeCompare;
    }

    // Sort based on name.
    int nameCompare = this.getName().compareTo(m.getName());
    if (nameCompare != 0) {
      return nameCompare;
    }

    // Sort based on type.
    int miscTypeCompare = this.getMiscType().compareTo(m.getMiscType());
    if (miscTypeCompare != 0) {
      return miscTypeCompare;
    }

    // Sort based on units.
    int unitCompare = this.getDisplayUnits().compareTo(m.getDisplayUnits());
    if (unitCompare != 0) {
      return unitCompare;
    }

    // Equal.
    return 0;
  }

  @Override
  public int getTime() {
    return time;
  }

  @Override
  public void setTime(int i) {
    this.time = i;
  }

  public String getTimeUnits() {
    if (use.equals(Misc.USE_PRIMARY) || use.equals(Misc.USE_SECONDARY)) {
      return Units.DAYS;
    }
    else if (use.equals(Misc.USE_BOIL) || use.equals(Misc.USE_MASH)) {
      return Units.MINUTES;
    }
    else {
      return Units.MINUTES;
    }
  }

  @Override
  public void setShortDescription(String description) {
    if (description.isEmpty()) {
      description = "No description provided";
    }
    this.notes = description;
  }

  @Override
  public void setDisplayUnits(String units) {
    this.displayUnits = units;
  }
} public class Yeast extends Ingredient {

  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  // Name - Inherited
  // Version - Inherited
  // Amount - Inherited
  private String type;                // Ale, Lager, etc
  private String form;                // Liquid, Dry, etc

  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private double minTemp;
  private double maxTemp;
  private double attenuation;              // Percentage: x / 100
  private String notes;
  private String bestFor;
  private String productid;
  private String lab;

  // Custom Fields ==================================================
  // ================================================================

  // Static values =================================================
  // ===============================================================
  public static final String TYPE_ALE = "Ale";
  public static final String TYPE_LAGER = "Lager";
  public static final String TYPE_WHEAT = "Wheat";
  public static final String TYPE_WINE = "Wine";
  public static final String TYPE_CHAMPAGNE = "Champagne";

  public static final String FORM_LIQUID = "Liquid";
  public static final String FORM_DRY = "Dry";
  public static final String FORM_SLANT = "Slant";
  public static final String FORM_CULTURE = "Culture";

  public Yeast(String name) {
    super(name);
    this.type = TYPE_ALE;
    this.form = FORM_LIQUID;
    this.amount = 0;
    this.minTemp = 0;
    this.maxTemp = 0;
    this.attenuation = 75;
    this.notes = "";
    this.bestFor = "";
    this.productid = "Unknown ID";
    this.lab = "Unknown lab";
  }

  public Yeast(Parcel p) {
    super(p);
    type = p.readString();
    form = p.readString();
    minTemp = p.readDouble();
    maxTemp = p.readDouble();
    attenuation = p.readDouble();
    notes = p.readString();
    bestFor = p.readString();
    productid = p.readString();
    lab = p.readString();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    super.writeToParcel(p, flags);
    p.writeString(type);                // Ale, Lager, etc
    p.writeString(form);                // Liquid, Dry, etc
    p.writeDouble(minTemp);
    p.writeDouble(maxTemp);
    p.writeDouble(attenuation);              // Percentage: x / 100
    p.writeString(notes);
    p.writeString(bestFor);
    p.writeString(productid);
    p.writeString(lab);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  public static final Parcelable.Creator<Yeast> CREATOR =
          new Parcelable.Creator<Yeast>() {
            @Override
            public Yeast createFromParcel(Parcel p) {
              return new Yeast(p);
            }

            @Override
            public Yeast[] newArray(int size) {
              return null;
            }
          };

  @Override
  public String toString() {
    return this.getName();
  }

  @Override
  public String getType() {
    return Ingredient.YEAST;
  }

  @Override
  public String getShortDescription() {
    String s = this.getLaboratory();
    s += " " + this.getProductId();

    if (s.length() > 3) {
      s += ": ";
    }
    s += this.notes;
    return s;
  }

  public String getArrayAdapterDescription() {
    String s = getLaboratory() + ", " + getProductId();

    if (s.length() < 3) {
      s = String.format("%2.2f", this.getAttenuation()) + "% attenuation";
    }

    return s;
  }

  public String getBeerXmlStandardUnits() {
    return Units.LITERS;
  }

  public double getDisplayAmount() {
    // TODO: We just assume a single yeast pkg
    return 1;
  }

  public double getBeerXmlStandardAmount() {
    return this.amount;
  }

  public void setDisplayAmount(double amt) {
    this.amount = amt;
  }

  public void setBeerXmlStandardAmount(double amt) {
    this.amount = amt;
  }

  @Override
  public String getDisplayUnits() {
    return Units.PACKAGES;
  }

  @Override
  public int hashCode() {
    int hc = this.getName().hashCode();
    hc = hc ^ (int) (this.getAttenuation() * 1234);
    return hc;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof Yeast) {
      return this.compareTo((Yeast) o) == 0;
    }
    return false;
  }

  @Override
  public int compareTo(Ingredient other) {
    // If not the same type of Ingredient, sort based on Ingredient type.
    int typeCompare = this.getType().compareTo(other.getType());
    if (typeCompare != 0) {
      return typeCompare;
    }
    Yeast y = (Yeast) other;

    // If they are both Yeast, sort based on attenuation.
    int attnCompare = Double.compare(this.getAttenuation(), y.getAttenuation());
    if (attnCompare != 0) {
      return attnCompare;
    }

    // Sort based on name.
    int nameCompare = this.getName().compareTo(y.getName());
    if (nameCompare != 0) {
      return nameCompare;
    }

    // Equal.
    return 0;
  }

  @Override
  public int getTime() {
    return this.time;
  }

  @Override
  public void setTime(int time) {
    this.time = time;
  }

  @Override
  public void setShortDescription(String description) {
    this.notes = description;
  }

  @Override
  public void setDisplayUnits(String units) {

  }

  /**
   * @return the type
   */
  public String getYeastType() {
    return type;
  }

  /**
   * @param type
   *         the type to set
   */
  public void setType(String type) {
    this.type = type;
  }

  /**
   * @return the form
   */
  public String getForm() {
    return form;
  }

  /**
   * @param form
   *         the form to set
   */
  public void setForm(String form) {
    this.form = form;
  }

  /**
   * @return the minTemp
   */
  public double getMinTemp() {
    return minTemp;
  }

  /**
   * @param minTemp
   *         the minTemp to set
   */
  public void setMinTemp(double minTemp) {
    this.minTemp = minTemp;
  }

  /**
   * @return the maxTemp
   */
  public double getMaxTemp() {
    return maxTemp;
  }

  /**
   * @param maxTemp
   *         the maxTemp to set
   */
  public void setMaxTemp(double maxTemp) {
    this.maxTemp = maxTemp;
  }

  public int getBeerXmlStandardFermentationTemp() {
    return (int) ((maxTemp + minTemp) / 2);
  }

  public int getDisplayFermentationTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return (int) Units.celsiusToFahrenheit(((maxTemp + minTemp) / 2));
    }
    else {
      return (int) ((maxTemp + minTemp) / 2);
    }
  }

  /**
   * @return the attenuation
   */
  public double getAttenuation() {
    return attenuation;
  }

  /**
   * @param attenuation
   *         the attenuation to set
   */
  public void setAttenuation(double attenuation) {
    this.attenuation = attenuation;
  }

  /**
   * @return the notes
   */
  public String getNotes() {
    return notes;
  }

  /**
   * @param notes
   *         the notes to set
   */
  public void setNotes(String notes) {
    this.notes = notes;
  }

  /**
   * f
   *
   * @return the bestFor
   */
  public String getBestFor() {
    return bestFor;
  }

  /**
   * @param bestFor
   *         the bestFor to set
   */
  public void setBestFor(String bestFor) {
    this.bestFor = bestFor;
  }

  public void setProductId(String s) {
    this.productid = s;
  }

  public void setLaboratory(String s) {
    this.lab = s;
  }

  public String getLaboratory() {
    return this.lab;
  }

  public String getProductId() {
    return this.productid;
  }
} public class BeerStyle implements Parcelable {

  // Categories based on beerXMl standard
  private String name;
  private String category;
  private Integer version;
  private String categoryNumber;
  private String styleLetter;
  private String styleGuide;
  private String type;
  private String notes;
  private String profile;
  private String ingredients;
  private String examples;

  // Reccomended values
  private double MinOg;
  private double MaxOg;
  private double MinFg;
  private double MaxFg;
  private double MinIbu;
  private double MaxIbu;
  private double minColor;
  private double maxColor;
  private double minAbv;
  private double maxAbv;

  // More
  private long ownerId;

  // Defines
  public static String TYPE_ALE = "Ale";
  public static String TYPE_LAGER = "Lager";
  public static String TYPE_MEAD = "Mead";
  public static String TYPE_WHEAT = "Wheat";
  public static String TYPE_MIXED = "Mixed";
  public static String TYPE_CIDER = "Cider";

  public BeerStyle(String name) {
    setName(name);
    setType("");
    setCategory("");
    setStyleLetter("");
    setNotes("");
    setExamples("");
    setIngredients("");
    setProfile("");
    setStyleGuide("");
    setCategoryNumber("");
    setVersion(1);
    setOwnerId(- 1);

    this.MinOg = 1;
    this.MaxOg = 2;
    this.MinFg = 1;
    this.MaxFg = 2;
    this.MinIbu = 0;
    this.MaxIbu = 200;
    this.minColor = 0;
    this.maxColor = 100;
    this.minAbv = 0;
    this.maxAbv = 100;
  }

  public BeerStyle(Parcel p) {
    // Categories based on beerXMl standard
    name = p.readString();
    category = p.readString();
    version = p.readInt();
    categoryNumber = p.readString();
    styleLetter = p.readString();
    styleGuide = p.readString();
    type = p.readString();
    notes = p.readString();
    profile = p.readString();
    ingredients = p.readString();
    examples = p.readString();
    MinOg = p.readDouble();
    MaxOg = p.readDouble();
    MinFg = p.readDouble();
    MaxFg = p.readDouble();
    MinIbu = p.readDouble();
    MaxIbu = p.readDouble();
    minColor = p.readDouble();
    maxColor = p.readDouble();
    minAbv = p.readDouble();
    maxAbv = p.readDouble();
    ownerId = p.readLong();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    // Categories based on beerXMl standard
    p.writeString(name);
    p.writeString(category);
    p.writeInt(version);
    p.writeString(categoryNumber);
    p.writeString(styleLetter);
    p.writeString(styleGuide);
    p.writeString(type);
    p.writeString(notes);
    p.writeString(profile);
    p.writeString(ingredients);
    p.writeString(examples);
    p.writeDouble(MinOg);
    p.writeDouble(MaxOg);
    p.writeDouble(MinFg);
    p.writeDouble(MaxFg);
    p.writeDouble(MinIbu);
    p.writeDouble(MaxIbu);
    p.writeDouble(minColor);
    p.writeDouble(maxColor);
    p.writeDouble(minAbv);
    p.writeDouble(maxAbv);
    p.writeLong(ownerId);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  public static final Parcelable.Creator<BeerStyle> CREATOR =
          new Parcelable.Creator<BeerStyle>() {
            @Override
            public BeerStyle createFromParcel(Parcel p) {
              return new BeerStyle(p);
            }

            @Override
            public BeerStyle[] newArray(int size) {
              return new BeerStyle[]{};
            }
          };

  @Override
  public boolean equals(Object o) {
    // Fist make sure its a BeerStyle
    if (! (o instanceof BeerStyle)) {
      return false;
    }

    // Based only off the name
    if (this.toString().equals(o.toString())) {
      return true;
    }
    else {
      return false;
    }

  }

  public String toString() {
    return name;
  }

  public void setOwnerId(long i) {
    this.ownerId = i;
  }

  public long getOwnerId() {
    return this.ownerId;
  }

  public void setMinCarb(double d) {
    // TODO
  }

  public void setMaxCarb(double d) {
    // TODO
  }

  public void setName(String s) {
    this.name = s;
  }

  public String getName() {
    return this.name;
  }

  public void setType(String s) {
    this.type = s;
  }

  public void setCategory(String s) {
    this.category = s;
  }

  public void setStyleLetter(String s) {
    this.styleLetter = s;
  }

  public void setNotes(String s) {
    this.notes = s;
  }

  public void setExamples(String s) {
    this.examples = s;
  }

  public void setProfile(String s) {
    this.profile = s;
  }

  public void setCategoryNumber(String s) {
    this.categoryNumber = s;
  }

  public void setStyleGuide(String s) {
    this.styleGuide = s;
  }

  public void setIngredients(String s) {
    this.ingredients = s;
  }

  public void setVersion(int i) {
    this.version = i;
  }

  // Methods for getting individual mins and maxes
  public double getMinOg() {
    return MinOg;
  }

  public void setMinOg(double minGrav) {
    this.MinOg = minGrav;
  }

  public double getMaxOg() {
    return MaxOg;
  }

  public void setMaxOg(double maxGrav) {
    this.MaxOg = maxGrav;
  }

  public double getMinIbu() {
    return MinIbu;
  }

  public void setMinIbu(double MinIbu) {
    this.MinIbu = MinIbu;
  }

  public double getMaxIbu() {
    return MaxIbu;
  }

  public void setMaxIbu(double MaxIbu) {
    this.MaxIbu = MaxIbu;
  }

  public double getMinColor() {
    return minColor;
  }

  public void setMinColor(double minColor) {
    this.minColor = minColor;
  }

  public double getMaxColor() {
    return maxColor;
  }

  public void setMaxColor(double maxColor) {
    this.maxColor = maxColor;
  }

  public double getAverageColor() {
    return (this.minColor + this.maxColor) / 2;
  }

  public double getMinAbv() {
    return minAbv;
  }

  public void setMinAbv(double minAbv) {
    this.minAbv = minAbv;
  }

  public double getMaxAbv() {
    return maxAbv;
  }

  public void setMaxAbv(double maxAbv) {
    this.maxAbv = maxAbv;
  }

  public double getMaxFg() {
    return MaxFg;
  }

  public void setMaxFg(double MaxFg) {
    this.MaxFg = MaxFg;
  }

  public double getMinFg() {
    return MinFg;
  }

  public void setMinFg(double MinFg) {
    this.MinFg = MinFg;
  }

  public String getCategory() {
    return this.category;
  }

  public String getCatNum() {
    return this.categoryNumber;
  }

  public String getStyleLetter() {
    return this.styleLetter;
  }

  public String getStyleGuide() {
    return this.styleGuide;
  }

  public String getType() {
    return this.type;
  }

  public double getMinCarb() {
    return 0; // TODO
  }

  public double getMaxCarb() {
    return 0; // TODO
  }

  public String getNotes() {
    // Return the string without any newlines or tabs.
    return this.notes.trim()
            .replace("\n", "")
            .replace("\r", "")
            .replace("\t", " ")
            .replaceAll(" +", " ");
  }

  public String getProfile() {
    return this.profile.trim()
            .replace("\n", "")
            .replace("\r", "")
            .replace("\t", " ")
            .replaceAll(" +", " ");
  }

  public String getIngredients() {
    return this.ingredients.trim()
            .replace("\n", "")
            .replace("\r", "")
            .replace("\t", " ")
            .replaceAll(" +", " ");
  }

  public String getExamples() {
    return this.examples.trim()
            .replace("\n", "")
            .replace("\r", "")
            .replace("\t", " ")
            .replaceAll(" +", " ");
  }
} public class MashProfile implements Parcelable {
  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  private String name;                     // profile name
  private Integer version;                 // XML Version -- 1
  private double grainTemp;                // Grain temp in C
  private ArrayList<MashStep> mashSteps;   // List of steps

  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private double tunTemp;         // TUN Temperature in C
  private double spargeTemp;      // Sparge Temp in C
  private double pH;              // pH of water
  private double tunWeight;       // Weight of TUN in kG
  private double tunSpecificHeat; // Specific heat of TUN
  private String notes;           // Notes
  private Boolean equipAdj;       // Adjust for heating of equip?

  // Custom Fields ==================================================
  // ================================================================
  private long id;                  // id for use in database
  private long ownerId;             // id for parent recipe
  private String mashType;          // one of infusion, decoction, temperature
  private String spargeType;        // one of batch, fly
  private Recipe recipe;            // Recipe which owns this mash.

  // Static values =================================================
  // ===============================================================
  public static String MASH_TYPE_INFUSION = "Infusion";
  public static String MASH_TYPE_DECOCTION = "Decoction";
  public static String MASH_TYPE_TEMPERATURE = "Temperature";
  public static String MASH_TYPE_BIAB = "BIAB";
  public static String SPARGE_TYPE_BATCH = "Batch";
  public static String SPARGE_TYPE_FLY = "Fly";
  public static String SPARGE_TYPE_BIAB = "BIAB";

  // Basic Constructor
  public MashProfile(Recipe r) {
    this.setName("New Mash Profile");
    this.setVersion(1);
    this.setBeerXmlStandardGrainTemp(20);
    this.mashSteps = new ArrayList<MashStep>();
    this.setBeerXmlStandardTunTemp(20);
    this.setBeerXmlStandardSpargeTemp(75.5555);
    this.setpH(7);
    this.setBeerXmlStandardTunWeight(0);
    this.setBeerXmlStandardTunSpecHeat(0);
    this.setEquipmentAdjust(false);
    this.setNotes("");
    this.id = - 1;
    this.ownerId = - 1;
    this.mashType = MASH_TYPE_INFUSION;
    this.spargeType = SPARGE_TYPE_BATCH;
    this.recipe = r;
  }

  public MashProfile() {
    this(new Recipe());
  }

  public MashProfile(Parcel p) {
    name = p.readString();
    version = p.readInt();
    grainTemp = p.readDouble();

    mashSteps = new ArrayList<MashStep>();
    p.readTypedList(mashSteps, MashStep.CREATOR);

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    tunTemp = p.readDouble();
    spargeTemp = p.readDouble();
    pH = p.readDouble();
    tunWeight = p.readDouble();
    tunSpecificHeat = p.readDouble();
    notes = p.readString();
    equipAdj = (p.readInt() > 0 ? true : false);

    // Custom Fields ==================================================
    // ================================================================
    id = p.readLong();
    ownerId = p.readLong();
    mashType = p.readString();
    spargeType = p.readString();
    // Don't read recipe because it recurses.
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    p.writeString(name);                // profile name
    p.writeInt(version);              // XML Version -- 1
    p.writeDouble(grainTemp);               // Grain temp in C
    p.writeTypedList(mashSteps);            // List of steps

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    p.writeDouble(tunTemp);        // TUN Temperature in C
    p.writeDouble(spargeTemp);      // Sparge Temp in C
    p.writeDouble(pH);              // pH of water
    p.writeDouble(tunWeight);       // Weight of TUN in kG
    p.writeDouble(tunSpecificHeat); // Specific heat of TUN
    p.writeString(notes);      // Notes
    p.writeInt(equipAdj ? 1 : 0);   // Adjust for heating of equip?

    // Custom Fields ==================================================
    // ================================================================
    p.writeLong(id);                  // id for use in database
    p.writeLong(ownerId);        // id for parent recipe
    p.writeString(mashType);
    p.writeString(spargeType);
    // Don't write recipe because it recurses.
  }

  @Override
  public int describeContents() {
    return 0;
  }

  public static final Parcelable.Creator<MashProfile> CREATOR =
          new Parcelable.Creator<MashProfile>() {
            @Override
            public MashProfile createFromParcel(Parcel p) {
              return new MashProfile(p);
            }

            @Override
            public MashProfile[] newArray(int size) {
              return new MashProfile[size];
            }
          };

  @Override
  public String toString() {
    return this.name;
  }

  @Override
  public boolean equals(Object o) {
    if (! (o instanceof MashProfile)) {
      return false;
    }

    MashProfile other = (MashProfile) o;
    if (this.hashCode() != other.hashCode()) {
      return false;
    }
    return true;
  }

  @Override
  public int hashCode() {
    int hc = this.name.hashCode();
    for (MashStep m : this.mashSteps) {
      hc ^= m.hashCode();
    }
    return hc;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  public void setVersion(Integer v) {
    this.version = v;
  }

  public Integer getVersion() {
    return this.version;
  }

  public void setBeerXmlStandardGrainTemp(double temp) {
    this.grainTemp = temp;
  }

  public void setDisplayGrainTemp(double temp) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.grainTemp = Units.fahrenheitToCelsius(temp);
    }
    else {
      this.grainTemp = temp;
    }
  }

  public double getBeerXmlStandardGrainTemp() {
    return this.grainTemp;
  }

  public double getDisplayGrainTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.grainTemp);
    }
    else {
      return this.grainTemp;
    }
  }

  public String getMashType() {
    return this.mashType;
  }

  public String getSpargeType() {
    return this.spargeType;
  }

  public void setMashType(String s) {
    this.mashType = s;
  }

  public void setSpargeType(String s) {
    Log.d("MashProfile", "Sparge type set: " + s);
    this.spargeType = s;
  }

  public void setBeerXmlStandardTunTemp(double temp) {
    this.tunTemp = temp;
  }

  public void setDisplayTunTemp(double temp) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.tunTemp = Units.fahrenheitToCelsius(temp);
    }
    else {
      this.tunTemp = temp;
    }
  }

  public double getBeerXmlStandardTunTemp() {
    return this.tunTemp;
  }

  public double getDisplayTunTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.tunTemp);
    }
    else {
      return this.tunTemp;
    }
  }

  public void setBeerXmlStandardSpargeTemp(double temp) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.spargeTemp = temp;
    }
  }

  public void setDisplaySpargeTemp(double temp) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.spargeTemp = Units.fahrenheitToCelsius(temp);
    }
    else {
      this.spargeTemp = temp;
    }
  }

  public double getDisplaySpargeTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.spargeTemp);
    }
    else {
      return this.spargeTemp;
    }
  }

  public double getBeerXmlStandardSpargeTemp() {
    return this.spargeTemp;
  }

  public void setpH(double pH) {
    this.pH = pH;
  }

  public double getpH() {
    return this.pH;
  }

  public void setBeerXmlStandardTunWeight(double weight) {
    this.tunWeight = weight;
  }

  public void setDisplayTunWeight(double weight) {
    if (Units.getWeightUnits().equals(Units.POUNDS)) {
      this.tunWeight = Units.poundsToKilos(weight);
    }
    else {
      this.tunWeight = weight;
    }
  }

  public double getBeerXmlStandardTunWeight() {
    return this.tunWeight;
  }

  public double getDisplayTunWeight() {
    if (Units.getWeightUnits().equals(Units.POUNDS)) {
      return Units.kilosToPounds(this.tunWeight);
    }
    else {
      return this.tunWeight;
    }
  }

  public void setBeerXmlStandardTunSpecHeat(double heat) {
    this.tunSpecificHeat = heat;
  }

  public double getBeerXmlStandardTunSpecHeat() {
    // Cal / (g * C)
    return this.tunSpecificHeat;
  }

  public void setEquipmentAdjust(boolean adj) {
    this.equipAdj = adj;
  }

  public Boolean getEquipmentAdjust() {
    return this.equipAdj;
  }

  public void setId(long id) {
    this.id = id;
  }

  public long getId() {
    return this.id;
  }

  public void setOwnerId(long id) {
    this.ownerId = id;
  }

  public long getOwnerId() {
    return this.ownerId;
  }

  public void setNotes(String s) {
    this.notes = s;
  }

  public String getNotes() {
    return this.notes;
  }

  public int getNumberOfSteps() {
    return this.mashSteps.size();
  }

  public void setRecipe(Recipe r) {
    this.recipe = r;
    for (MashStep m : this.mashSteps) {
      m.setRecipe(this.recipe);
    }
  }

  public ArrayList<MashStep> getMashStepList() {
    return this.mashSteps;
  }

  public void clearMashSteps() {
    this.mashSteps = new ArrayList<MashStep>();
  }

  /**
   * Sets mash step list to given list.  Assumes list is in the desired order and overrides orders
   * if reorder set to true
   *
   * @param list
   */
  public void setMashStepList(ArrayList<MashStep> list) {
    this.mashSteps = list;
    for (MashStep s : this.mashSteps) {
      s.setRecipe(this.recipe);
    }
    Collections.sort(this.mashSteps, new FromDatabaseMashStepComparator());
  }

  /**
   * Removes the given step, returns true if success
   *
   * @param step
   * @return
   */
  public boolean removeMashStep(MashStep step) {
    return this.mashSteps.remove(step);
  }

  public MashStep removeMashStep(int order) {
    return this.mashSteps.remove(order);
  }

  public void addMashStep(MashStep step) {
    step.setRecipe(this.recipe);
    this.mashSteps.add(step);
  }

  public void addMashStep(int order, MashStep step) {
    step.setRecipe(this.recipe);
    this.mashSteps.add(order, step);
  }

  public void save(Context c, long database) {
    Log.d("MashProfile", "Saving " + name + " to database " + database);
    if (this.id < 0) {
      // We haven't yet saved this.  Add it to the database.
      new DatabaseAPI(c).addMashProfile(database, this, this.getOwnerId());
    }
    else {
      // Already exists.  Update it.
      new DatabaseAPI(c).updateMashProfile(this, this.getOwnerId(), database);
    }
  }

  public void delete(Context c, long database) {
    new DatabaseAPI(c).deleteMashProfileFromDatabase(this, database);
  }
} public class MashStep implements Parcelable {
  // ================================================================
  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  private String name;              // profile name
  private int version;              // XML Version -- 1
  private String type;              // infusion, temp, decoc
  private double infuseAmount;      // Amount
  private double stepTemp;          // Temp for this step in C
  private double stepTime;          // Time for this step

  // ================================================================
  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private double rampTime;            // Time to ramp temp
  private double endTemp;             // Final temp for long steps
  private String description;         // Description of step
  private double waterToGrainRatio;   // Water to grain ratio (L/kg)
  private double decoctAmount;        // Amount of mash to decoct. (L)

  // ================================================================
  // Custom Fields ==================================================
  // ================================================================
  private long ownerId;             // id for parent mash profile
  private long id;                  // id for use in database
  public int order;                 // Order in step list
  private double infuseTemp;        // Temperature of infuse water
  private boolean calcInfuseTemp;   // Auto calculate the infusion temperature if true.
  private boolean calcInfuseAmt;    // Auto calculate the infusion amount if true.
  private boolean calcDecoctAmt;    // Auto calculate the amount to decoct if true.
  private Recipe recipe;            // Reference to the recipe that owns this mash.

  // ================================================================
  // Static values ==================================================
  // ================================================================
  public static String INFUSION = "Infusion";
  public static String TEMPERATURE = "Temperature";
  public static String DECOCTION = "Decoction";

  // Basic Constructor
  public MashStep(Recipe r) {
    this.setName("Mash Step (" + (r.getMashProfile().getMashStepList().size() + 1) + ")");
    this.setVersion(1);
    this.setType(MashStep.INFUSION);
    this.setDisplayInfuseAmount(0);
    this.setBeerXmlStandardStepTemp(65.555556);
    this.setStepTime(60);
    this.setRampTime(0);
    this.setBeerXmlStandardEndTemp(0.0);
    this.setDescription("");
    this.setBeerXmlStandardWaterToGrainRatio(2.60793889);
    this.infuseTemp = 0.0;
    this.id = - 1;
    this.ownerId = - 1;
    this.order = 1;
    this.decoctAmount = 0;
    this.calcInfuseAmt = true;
    this.calcInfuseTemp = true;
    this.calcDecoctAmt = true;
    this.recipe = r;
  }

  // Only use this when we don't have a mash profile to
  // use!
  public MashStep() {
    this(new Recipe());
  }

  public MashStep(Parcel p) {
    // Beer XML 1.0 Required Fields ===================================
    // ================================================================
    name = p.readString();
    version = p.readInt();
    type = p.readString();
    infuseAmount = p.readDouble();
    stepTemp = p.readDouble();
    stepTime = p.readDouble();

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    rampTime = p.readDouble();
    endTemp = p.readDouble();
    description = p.readString();
    waterToGrainRatio = p.readDouble();
    decoctAmount = p.readDouble();

    // Custom Fields ==================================================
    // ================================================================
    ownerId = p.readLong();
    id = p.readLong();
    order = p.readInt();
    infuseTemp = p.readDouble();
    calcInfuseTemp = p.readInt() == 0 ? false : true;
    calcInfuseAmt = p.readInt() == 0 ? false : true;
    calcDecoctAmt = p.readInt() == 0 ? false : true;
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    // Beer XML 1.0 Required Fields ===================================
    // ================================================================
    p.writeString(name);                // profile name
    p.writeInt(version);              // XML Version -- 1
    p.writeString(type);                    // infusion, temp, decoc
    p.writeDouble(infuseAmount);            // Amount
    p.writeDouble(stepTemp);        // Temp for this step in C
    p.writeDouble(stepTime);        // Time for this step

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    p.writeDouble(rampTime);        // Time to ramp temp
    p.writeDouble(endTemp);             // Final temp for long steps
    p.writeString(description);         // Description of step
    p.writeDouble(waterToGrainRatio);   // Water to grain ratio (L/kg)
    p.writeDouble(decoctAmount);        // Amount of water to decoct.

    // Custom Fields ==================================================
    // ================================================================
    p.writeLong(ownerId);        // id for parent mash profile
    p.writeLong(id);                    // id for use in database
    p.writeInt(order);                  // Order in step list
    p.writeDouble(infuseTemp);
    p.writeInt(calcInfuseTemp ? 1 : 0);
    p.writeInt(calcInfuseAmt ? 1 : 0);
    p.writeInt(calcDecoctAmt ? 1 : 0);
  }

  public static final Parcelable.Creator<MashStep> CREATOR =
          new Parcelable.Creator<MashStep>() {
            @Override
            public MashStep createFromParcel(Parcel p) {
              return new MashStep(p);
            }

            @Override
            public MashStep[] newArray(int size) {
              return new MashStep[size];
            }
          };

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public boolean equals(Object o) {
    // Non-MashStep objects cannot equal a MashStep.
    if (! (o instanceof MashStep)) {
      return false;
    }

    // Comparing to a MashStep - cast the given Object.
    MashStep other = (MashStep) o;

    // Both are MashStep objects - compare important fields.
    if (! this.getName().equals(other.getName())) {
      Log.d("MashStep", "MashStep.equals(): " + this.getName() + " != " + other.getName());
      return false;
    }
    else if (this.getStepTime() != other.getStepTime()) {
      Log.d("MashStep", "MashStep.equals(): " + this.getStepTime() + " != " + other.getStepTime());
      return false;
    }
    else if (this.getBeerXmlStandardStepTemp() != other.getBeerXmlStandardStepTemp()) {
      Log.d("MashStep", "MashStep.equals(): " + this.getBeerXmlStandardStepTemp() + " != " + other.getBeerXmlStandardStepTemp());
      return false;
    }
    else if (! this.getType().equals(other.getType())) {
      Log.d("MashStep", "MashStep.equals(): " + this.getType() + " != " + other.getType());
      return false;
    }

    // All index fields match - these objects are equal.
    return true;
  }

  @Override
  public String toString() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  public void setVersion(int version) {
    this.version = version;
  }

  public int getVersion() {
    return this.version;
  }

  public void setType(String type) {
    this.type = type;
  }

  public String getType() {
    return this.type;
  }

  public double getDisplayDecoctAmount() {
    // If we are autocalculating.
    if (this.calcDecoctAmt) {
      return this.calculateDecoctAmount();
    }

    // We're not auto-calculating.
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.decoctAmount);
    }
    else {
      return this.decoctAmount;
    }
  }

  public void setDisplayDecoctAmount(double amt) {
    Log.d("MashStep", "Setting display decoction amount: " + amt);
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.decoctAmount = Units.gallonsToLiters(amt);
    }
    else {
      this.decoctAmount = amt;
    }
  }

  public void setBeerXmlDecoctAmount(double amt) {
    this.decoctAmount = amt;
  }

  public double getBeerXmlDecoctAmount() {
    return this.decoctAmount;
  }

  public void setDisplayInfuseAmount(double amt) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.infuseAmount = Units.gallonsToLiters(amt);
    }
    else {
      this.infuseAmount = amt;
    }
  }

  public double getDisplayAmount() {
    if (this.getType().equals(DECOCTION)) {
      Log.d("MashStep", "Returning display decoction amount: " + this.getDisplayDecoctAmount());
      return this.getDisplayDecoctAmount();
    }
    else if (this.getType().equals(INFUSION)) {
      Log.d("MashStep", "Returning display infusion amount: " + this.getDisplayInfuseAmount());
      return this.getDisplayInfuseAmount();
    }
    else if (this.getType().equals(TEMPERATURE)) {
      Log.d("MashStep", "Temperature mash, returning 0 for display amount");
      return 0;
    }
    Log.d("MashStep", "Invalid type: " + this.getType() + ".  Returning -1 for display amount");
    return - 1;
  }

  public double getDisplayInfuseAmount() {
    // No infuse amount for decoction steps. Ever.
    if (this.getType().equals(MashStep.DECOCTION)) {
      return 0;
    }

    // If we are autocalculating.
    if (this.calcInfuseAmt) {
      return this.calculateInfuseAmount();
    }

    // If we're not auto-calculating.
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.infuseAmount);
    }
    else {
      return this.infuseAmount;
    }
  }

  /* Calculates the infusion amount based on
  /* water to grain ratio, water temp, water to add,
  /* and the step temperature.
  /* Also sets this.infuseAmount to the correct value. */
  public double calculateInfuseAmount() {
    // We perform different calculations if this is the initial infusion.
    double amt = - 1;
    if (this.firstInList() &&
            ! this.recipe.getMashProfile().getMashType().equals(MashProfile.MASH_TYPE_BIAB)) {
      // Initial infusion for a non-biab mash. Water is constant * amount of grain.
      amt = this.getBeerXmlStandardWaterToGrainRatio() * this.getBeerXmlStandardMashWeight();
    }
    else if (this.firstInList() &&
            this.recipe.getMashProfile().getMashType().equals(MashProfile.MASH_TYPE_BIAB)) {
      // Initial infusion for a BIAB mash.
      // Water is boil volume + grain absorption.
      amt = this.recipe.getBeerXmlStandardBoilSize();

      // Estimate ~1 liter per kg absobtion.
      // TODO: Get this from equipment profile.
      amt += this.getBeerXmlStandardMashWeight();
    }
    else {
      // The actual temperature of the water being infused.
      double actualInfuseTemp = calcInfuseTemp ? calculateBXSInfuseTemp() : getBeerXmlStandardInfuseTemp();

      // Not initial infusion. Calculate water to add to reach appropriate temp.
      try {
        amt = (this.getBeerXmlStandardStepTemp() - getPreviousStep().getBeerXmlStandardStepTemp());
      } catch (Exception e) {
        e.printStackTrace();
      }
      amt = amt * (.41 * this.getBeerXmlStandardMashWeight() + this.getBXSTotalWaterInMash());
      amt = amt / (actualInfuseTemp - this.getBeerXmlStandardStepTemp());
    }

    // Set BXL amount so that database is consistent.
    this.infuseAmount = amt;

    // Use appropriate units.
    if (Units.getVolumeUnits().equals(Units.LITERS)) {
      return amt;
    }
    else {
      return Units.litersToGallons(amt);
    }
  }

  /* Calculates the decoction amount */
  public double calculateDecoctAmount() {
    Log.d("MashStep", "Auto-calculating decoct amount");
    /**
     * F = (TS – TI) / (TB – TI – X)
     *
     * Where f is the fraction, TS is the target step temperature, TI is the initial (current) temperature,
     * TB is the temperature of the boiling mash and X is an equipment dependent parameter (typically 18F or 10C).
     */
    double target = this.getBeerXmlStandardStepTemp();
    double initial = 0;
    try {
      initial = this.getPreviousStep().getBeerXmlStandardStepTemp();
    } catch (Exception e) {
      // Attempted to calculate decoct amount for first step in MashProfile.  This isn't
      // allowed (the first step must be an infusion step).  Handle this
      // somewhat gracefully, as this can occur temporarily during recipe formulation.
      e.printStackTrace();
      return 0;
    }
    double boiling = 99; // Boiling temperature of mash (C).
    double X = 10; // TODO: Equipment factor - accounts for heat absorbtion of mash tun.
    double grainDensity = .43; // .43kg / 1L
    double waterVolume = getBXSTotalWaterInMash(); // L
    double grainMass = (waterVolume / waterToGrainRatio); // kg
    double grainVolume = grainMass / grainDensity; // L
    double totalVolume = waterVolume + grainVolume; // L

    // This calculates the ratio of "mash to boil" / "mash to leave in tun".
    double fraction = (target - initial) / (boiling - initial - X);

    // Convert this ratio to "Mash to boil" / "Total Mash"
    fraction = (fraction) / (1 + fraction);

    // Return the fraction of mash, adjusted for the selected units.
    if (Units.getUnitSystem().equals(Units.METRIC)) {
      return fraction * totalVolume; // Return Liters of mash.
    }
    else {
      return fraction * Units.litersToGallons(totalVolume); // Return Gallons of mash.
    }
  }

  /**
   * Calculates the infusion temperature for both initial infusion, and water adds.
   * http://www.howtobrew.com/section3/chapter16-3.html
   */
  public double calculateInfuseTemp() {
    // We perform different calculations if this is the initial infusion.
    double temp = 0;
    if (this.firstInList()) {
      // Initial infusion.
      // TODO: For now, we don't have equipment so we combine tun / grain temp for calculation.
      double tunTemp = .7 * this.recipe.getMashProfile().getBeerXmlStandardGrainTemp() +
              .3 * this.recipe.getMashProfile().getBeerXmlStandardTunTemp();
      temp = (.41) / (this.getBeerXmlStandardWaterToGrainRatio());
      temp = temp * (this.getBeerXmlStandardStepTemp() - tunTemp) + this.getBeerXmlStandardStepTemp();
    }
    else {
      // Not initial infusion.  Assume boiling water to make
      // calculation easier.  If the next step has a LOWER temperature,
      // use room temperature water (72F).
      try {
        if (getPreviousStep().getBeerXmlStandardStepTemp() < this.getBeerXmlStandardStepTemp()) {
          temp = 100;
        }
        else {
          temp = 22.2222;
        }
      } catch (Exception e) {
        temp = - 1;
      }
    }

    // Set the infuse temperature.
    this.infuseTemp = temp;

    // Use appropriate units.
    if (Units.getTemperatureUnits().equals(Units.CELSIUS)) {
      return temp;
    }
    else {
      return Units.celsiusToFahrenheit(temp);
    }
  }

  private double calculateBXSInfuseTemp() {
    if (Units.getTemperatureUnits().equals(Units.CELSIUS)) {
      return this.calculateInfuseTemp();
    }
    else {
      return Units.fahrenheitToCelsius(this.calculateInfuseTemp());
    }
  }

  public boolean firstInList() {
    return this.getOrder() == 0;
  }

  public void setBeerXmlStandardInfuseAmount(double amt) {
    this.infuseAmount = amt;
  }

  public double getBeerXmlStandardInfuseAmount() {
    if (this.calcInfuseAmt) {
      calculateInfuseAmount();
    }
    return this.infuseAmount;
  }

  public double getDisplayInfuseTemp() {
    if (this.calcInfuseTemp) {
      return Math.round(this.calculateInfuseTemp());
    }

    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Math.round(Units.celsiusToFahrenheit(this.infuseTemp));
    }
    else {
      return Math.round(this.infuseTemp);
    }
  }

  public void setDisplayInfuseTemp(double d) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.infuseTemp = Units.fahrenheitToCelsius(d);
    }
    else {
      this.infuseTemp = d;
    }
  }

  public double getBeerXmlStandardInfuseTemp() {
    return this.infuseTemp;
  }

  public void setBeerXmlStandardInfuseTemp(double d) {
    this.infuseTemp = d;
  }

  public void setAutoCalcInfuseTemp(boolean b) {
    this.calcInfuseTemp = b;
  }

  public void setAutoCalcInfuseAmt(boolean b) {
    this.calcInfuseAmt = b;
  }

  public void setAutoCalcDecoctAmt(boolean b) {
    this.calcDecoctAmt = b;
  }

  public boolean getAutoCalcDecoctAmt() {
    return this.calcDecoctAmt;
  }

  public boolean getAutoCalcInfuseTemp() {
    return this.calcInfuseTemp;
  }

  public boolean getAutoCalcInfuseAmt() {
    return this.calcInfuseAmt;
  }

  public void setDisplayStepTemp(double temp) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.stepTemp = Units.fahrenheitToCelsius(temp);
    }
    else {
      this.stepTemp = temp;
    }
  }

  public double getDisplayStepTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.stepTemp);
    }
    else {
      return this.stepTemp;
    }
  }

  public void setBeerXmlStandardStepTemp(double temp) {
    this.stepTemp = temp;
  }

  public double getBeerXmlStandardStepTemp() {
    return this.stepTemp;
  }

  public double getBeerXmlStandardWaterToGrainRatio() {
    // If this is the first in the list, use the configured value.
    // Otherwise, we need to calculate it based on the water added.
    if (this.firstInList() && ! this.recipe.getMashProfile().getMashType().equals(MashProfile.MASH_TYPE_BIAB)) {
      return this.waterToGrainRatio;
    }
    return (this.getBeerXmlStandardInfuseAmount() + this.getBXSTotalWaterInMash()) / this.getBeerXmlStandardMashWeight();
  }

  public double getDisplayWaterToGrainRatio() {
    if (Units.getUnitSystem().equals(Units.IMPERIAL)) {
      return Units.LPKGtoQPLB(getBeerXmlStandardWaterToGrainRatio());
    }
    else {
      return getBeerXmlStandardWaterToGrainRatio();
    }
  }

  public void setBeerXmlStandardWaterToGrainRatio(double d) {
    // Don't update if less than 0. Impossible value.
    if (d <= 0) {
      return;
    }
    this.waterToGrainRatio = d;
  }

  public void setDisplayWaterToGrainRatio(double d) {
    if (d <= 0) {
      return;
    }
    if (Units.getUnitSystem().equals(Units.IMPERIAL)) {
      this.waterToGrainRatio = Units.QPLBtoLPKG(d);
    }
    else {
      this.waterToGrainRatio = d;
    }
  }

  public void setOrder(int i) {
    // Order is privately used for ordering mash steps
    // when they are received from the database.  Once they
    // are out of the db, we use the order in the list as the order.
    // When saved, the orders will be updated in the database.
    this.order = i;
  }

  public int getOrder() {
    return this.recipe.getMashProfile().getMashStepList().indexOf(this);
  }

  public void setDescription(String s) {
    this.description = s;
  }

  public String getDescription() {
    return this.description;
  }

  public void setStepTime(double time) {
    this.stepTime = time;
  }

  public double getStepTime() {
    return this.stepTime;
  }

  public void setRampTime(double time) {
    this.rampTime = time;
  }

  public double getRampTime() {
    return this.rampTime;
  }

  public void setBeerXmlStandardEndTemp(double temp) {
    this.endTemp = temp;
  }

  public double getBeerXmlStandardEndTemp() {
    return this.endTemp;
  }

  public void setId(long id) {
    this.id = id;
  }

  public long getId() {
    return this.id;
  }

  public void setOwnerId(long id) {
    this.ownerId = id;
  }

  public long getOwnerId() {
    return this.ownerId;
  }

  public void setRecipe(Recipe r) {
    this.recipe = r;
  }

  public Recipe getRecipe() {
    return this.recipe;
  }

  public double getBeerXmlStandardMashWeight() {
    // If there are no ingredients for this recipe yet, we need to use something!
    // Fake it, assume 12 pounds of grain by default.
    double weight = BrewCalculator.TotalBeerXmlMashWeight(this.recipe);
    return weight == 0 ? Units.poundsToKilos(12) : weight;
  }

  public MashStep getPreviousStep() throws Exception {
    if (this.firstInList()) {
      throw new Exception(); // TODO: This should throw a specific exception.
    }

    int idx = this.recipe.getMashProfile().getMashStepList().indexOf(this);
    return this.recipe.getMashProfile().getMashStepList().get(idx - 1);
  }

  public double getBXSTotalWaterInMash() {
    double amt = 0;

    for (MashStep step : this.recipe.getMashProfile().getMashStepList()) {
      if (step.equals(this)) {
        break;
      }
      amt += step.getBeerXmlStandardInfuseAmount();
    }
    Log.d("MashStep", "Step " + this.getName() + " has " + Units.litersToGallons(amt) + " gal in mash.");
    return amt;
  }
} public class Recipe implements Parcelable {
  // ===============================================================
  // Static values =================================================
  // ===============================================================
  public static final String EXTRACT = "Extract";
  public static final String ALL_GRAIN = "All Grain";
  public static final String PARTIAL_MASH = "Partial Mash";
  public static final int STAGE_PRIMARY = 1;
  public static final int STAGE_SECONDARY = 2;
  public static final int STAGE_TERTIARY = 3;
  public static final Parcelable.Creator<Recipe> CREATOR =
          new Parcelable.Creator<Recipe>() {
            @Override
            public Recipe createFromParcel(Parcel p) {
              return new Recipe(p);
            }

            @Override
            public Recipe[] newArray(int size) {
              return new Recipe[]{};
            }
          };

  // ================================================================
  // Beer XML 1.0 Required Fields ===================================
  // ================================================================
  private String name;                 // Recipe name
  private int version;                 // XML Version -- 1
  private String type;                 // Extract, Grain, Mash
  private BeerStyle style;             // Stout, Pilsner, etc.
  private String brewer;               // Brewer's name
  private double batchSize;            // Target size (L)
  private double boilSize;             // Pre-boil vol (L)
  private int boilTime;                // In Minutes
  private double efficiency;           // 100 for extract
  private ArrayList<Hop> hops;         // Hops used
  private ArrayList<Fermentable> fermentables;  // Fermentables used
  private ArrayList<Yeast> yeasts;     // Yeasts used
  private ArrayList<Misc> miscs;       // Misc ingredients used
  private ArrayList<Water> waters;     // Waters used
  private MashProfile mashProfile;     // Mash profile for non-extracts

  // ================================================================
  // Beer XML 1.0 Optional Fields ===================================
  // ================================================================
  private double OG;                // Original Gravity
  private double FG;                // Final Gravity
  private int fermentationStages;   // # of Fermentation stages
  private int primaryAge;           // Time in primary in days
  private double primaryTemp;       // Temp in primary in C
  private int secondaryAge;         // Time in Secondary in days
  private double secondaryTemp;     // Temp in secondary in C
  private int tertiaryAge;          // Time in tertiary in days
  private double tertiaryTemp;      // Temp in tertiary in C
  private String tasteNotes;        // Taste notes
  private int tasteRating;          // Taste score out of 50
  private int bottleAge;            // Bottle age in days
  private double bottleTemp;        // Bottle temp in C
  private boolean isForceCarbonated;// True if force carb is used
  private double carbonation;       // Volumes of carbonation
  private String brewDate;          // Date brewed
  private String primingSugarName;  // Name of sugar for priming
  private double primingSugarEquiv; // Equivalent amount of priming sugar to be used
  private double kegPrimingFactor;  // factor - use less sugar when kegging vs bottles
  private double carbonationTemp;   // Carbonation temperature in C
  private int calories;             // Calories (KiloCals)
  private String lastModified;      // Date last modified in database.

  // ================================================================
  // Custom Fields ==================================================
  // ================================================================
  private long id;                   // id for use in database
  private String notes;              // User input notes
  private int batchTime;             // Total length in weeks
  private double ABV;                // Alcohol by volume
  private double bitterness;         // Bitterness in IBU
  private double color;              // Color - SRM
  private InstructionGenerator instructionGenerator;
  private double measuredOG;         // Brew day stat: measured OG
  private double measuredFG;         // Brew stat: measured FG
  private double measuredVol;        // Measured final volume (L) of batch.
  private double steepTemp;          // Temperature to steep grains.

  // ================================================================
  // Fields for auto-calculation ====================================
  // ================================================================
  private boolean calculateBoilVolume;            // Calculate the boil volume automatically
  private boolean calculateStrikeVolume;          // Calculate strike vol automatically
  private boolean calculateStrikeTemp;            // Calculate strike temp automatically

  // Public constructors
  public Recipe(String s) {
    // ================================================================
    // Beer XML 1.0 Required Fields ===================================
    // ================================================================
    this.name = s;
    this.setVersion(1);
    this.setType(ALL_GRAIN);
    this.style = Constants.BEERSTYLE_OTHER;
    this.setBrewer("Unknown Brewer");
    this.setDisplayBatchSize(5);
    this.setDisplayBoilSize(2.5);
    this.setBoilTime(60);
    this.setEfficiency(70);
    this.hops = new ArrayList<Hop>();
    this.fermentables = new ArrayList<Fermentable>();
    this.yeasts = new ArrayList<Yeast>();
    this.miscs = new ArrayList<Misc>();
    this.waters = new ArrayList<Water>();
    this.mashProfile = new MashProfile(this);

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    this.OG = 1;
    this.setFG(1);
    this.setFermentationStages(1);
    this.primaryAge = 14;
    this.secondaryAge = 0;
    this.tertiaryAge = 0;
    this.primaryTemp = 21;
    this.secondaryTemp = 21;
    this.tertiaryTemp = 21;
    this.bottleAge = 14;
    this.brewDate = "";

    // Custom Fields ==================================================
    // ================================================================
    this.id = - 1;
    this.notes = "";
    this.tasteNotes = "";
    this.batchTime = 60;
    this.ABV = 0;
    this.bitterness = 0;
    this.color = 0;
    this.instructionGenerator = new InstructionGenerator(this);
    this.measuredOG = 0;
    this.measuredFG = 0;
    this.measuredVol = 0;
    this.steepTemp = Units.fahrenheitToCelsius(155);
    this.lastModified = new SimpleDateFormat(Constants.LAST_MODIFIED_DATE_FMT).format(new Date());

    // Fields for auto-calculation ====================================
    // ================================================================
    calculateBoilVolume = true;
    calculateStrikeVolume = false;
    calculateStrikeTemp = false;

    update();
  }

  // Constructor with no arguments!
  public Recipe() {
    this("New Recipe");
  }

  public Recipe(Parcel p) {
    hops = new ArrayList<Hop>();
    fermentables = new ArrayList<Fermentable>();
    yeasts = new ArrayList<Yeast>();
    miscs = new ArrayList<Misc>();
    waters = new ArrayList<Water>();

    name = p.readString();            // Recipe name
    version = p.readInt();          // XML Version -- 1
    type = p.readString();                // Extract, Grain, Mash
    style = p.readParcelable(BeerStyle.class.getClassLoader());    // Stout, Pilsner, etc.
    brewer = p.readString();            // Brewer's name
    batchSize = p.readDouble();
    boilSize = p.readDouble();
    boilTime = p.readInt();            // In Minutes
    efficiency = p.readDouble();          // 100 for extract
    p.readTypedList(hops, Hop.CREATOR);              // Hops used
    p.readTypedList(fermentables, Fermentable.CREATOR);      // Fermentables used
    p.readTypedList(yeasts, Yeast.CREATOR);            // Yeasts used
    p.readTypedList(miscs, Misc.CREATOR);           // Misc ingredients used
    p.readTypedList(waters, Water.CREATOR);           // Waters used
    mashProfile = p.readParcelable(MashProfile.class.getClassLoader()); // Mash profile for non-extracts
    mashProfile.setRecipe(this);

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    OG = p.readDouble();            // Original Gravity
    FG = p.readDouble();            // Final Gravity
    fermentationStages = p.readInt();   // # of Fermentation stages
    primaryAge = p.readInt();        // Time in primary in days
    primaryTemp = p.readDouble();      // Temp in primary in C
    secondaryAge = p.readInt();      // Time in Secondary in days
    secondaryTemp = p.readDouble();    // Temp in secondary in C
    tertiaryAge = p.readInt();      // Time in tertiary in days
    tertiaryTemp = p.readDouble();    // Temp in tertiary in C
    tasteNotes = p.readString();        // Taste notes
    tasteRating = p.readInt();          // Taste score out of 50
    bottleAge = p.readInt();            // Bottle age in days
    bottleTemp = p.readDouble();        // Bottle temp in C
    isForceCarbonated = p.readInt() > 0;  // True if force carb is used
    carbonation = p.readDouble();       // Volumes of carbonation
    brewDate = p.readString();          // Date brewed
    primingSugarName = p.readString();  // Name of sugar for priming
    primingSugarEquiv = p.readDouble(); // Equivalent amount of priming sugar to be used
    kegPrimingFactor = p.readDouble();  // factor - use less sugar when kegging vs bottles
    carbonationTemp = p.readDouble();   // Carbonation temperature in C
    calories = p.readInt();
    lastModified = p.readString();

    // Custom Fields ==================================================
    // ================================================================
    id = p.readLong();                  // id for use in database
    notes = p.readString();       // User input notes
    batchTime = p.readInt();            // Total length in weeks
    ABV = p.readDouble();                // Alcohol by volume
    bitterness = p.readDouble();         // Bitterness in IBU
    color = p.readDouble();              // Color - SRM
    // Instruction generator not included in parcel
    measuredOG = p.readDouble();         // Brew day stat: measured OG
    measuredFG = p.readDouble();         // Brew stat: measured FG
    measuredVol = p.readDouble();        // Brew stat: measured volume
    steepTemp = p.readDouble();          // Temperature to steep grains for extract recipes.

    // Fields for auto-calculation ====================================
    // ================================================================
    calculateBoilVolume = p.readInt() > 0;
    calculateStrikeVolume = p.readInt() > 0;
    calculateStrikeTemp = p.readInt() > 0;

    // Create instruction generator
    instructionGenerator = new InstructionGenerator(this);
    update();
  }

  @Override
  public void writeToParcel(Parcel p, int flags) {
    p.writeString(name);            // Recipe name
    p.writeInt(version);          // XML Version -- 1
    p.writeString(type);                // Extract, Grain, Mash
    p.writeParcelable(style, flags);    // Stout, Pilsner, etc.
    p.writeString(brewer);            // Brewer's name
    p.writeDouble(batchSize);           // Target size (L)
    p.writeDouble(boilSize);        // Pre-boil vol (L)
    p.writeInt(boilTime);            // In Minutes
    p.writeDouble(efficiency);          // 100 for extract
    p.writeTypedList(hops);             // Hops used
    p.writeTypedList(fermentables);     // Fermentables used
    p.writeTypedList(yeasts);           // Yeasts used
    p.writeTypedList(miscs);            // Misc ingredients used
    p.writeTypedList(waters);           // Waters used
    p.writeParcelable(mashProfile, flags); // Mash profile for non-extracts

    // Beer XML 1.0 Optional Fields ===================================
    // ================================================================
    p.writeDouble(OG);            // Original Gravity
    p.writeDouble(FG);            // Final Gravity
    p.writeInt(fermentationStages);   // # of Fermentation stages
    p.writeInt(primaryAge);        // Time in primary in days
    p.writeDouble(primaryTemp);      // Temp in primary in C
    p.writeInt(secondaryAge);      // Time in Secondary in days
    p.writeDouble(secondaryTemp);    // Temp in secondary in C
    p.writeInt(tertiaryAge);      // Time in tertiary in days
    p.writeDouble(tertiaryTemp);    // Temp in tertiary in C
    p.writeString(tasteNotes);        // Taste notes
    p.writeInt(tasteRating);          // Taste score out of 50
    p.writeInt(bottleAge);            // Bottle age in days
    p.writeDouble(bottleTemp);        // Bottle temp in C
    p.writeInt(isForceCarbonated ? 1 : 0);  // True if force carb is used
    p.writeDouble(carbonation);       // Volumes of carbonation
    p.writeString(brewDate);          // Date brewed
    p.writeString(primingSugarName);  // Name of sugar for priming
    p.writeDouble(primingSugarEquiv); // Equivalent amount of priming sugar to be used
    p.writeDouble(kegPrimingFactor);  // factor - use less sugar when kegging vs bottles
    p.writeDouble(carbonationTemp);   // Carbonation temperature in C
    p.writeInt(calories);             // Calories (KiloCals)
    p.writeString(lastModified);

    // Custom Fields ==================================================
    // ================================================================
    p.writeLong(id);                  // id for use in database
    p.writeString(notes);              // User input notes
    p.writeInt(batchTime);             // Total length in weeks
    p.writeDouble(ABV);                // Alcohol by volume
    p.writeDouble(bitterness);         // Bitterness in IBU
    p.writeDouble(color);              // Color - SRM
    // Instruction generator not included in parcel
    p.writeDouble(measuredOG);         // Brew day stat: measured OG
    p.writeDouble(measuredFG);         // Brew stat: measured FG
    p.writeDouble(measuredVol);        // Brew stat: measured volume
    p.writeDouble(steepTemp);          // Steep temperature for extract recipes.

    // Fields for auto-calculation ====================================
    // ================================================================
    p.writeInt(calculateBoilVolume ? 1 : 0);
    p.writeInt(calculateStrikeVolume ? 1 : 0);
    p.writeInt(calculateStrikeTemp ? 1 : 0);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  /**
   * Recipe objects are identified by the recipe name and the recipe's ID, as used when stored in
   * the database.  If a recipe has not been stored in the database, it will not necessarily have a
   * unique ID.
   */
  @Override
  public boolean equals(Object o) {
    // If the given object is not a Recipe, it cannot be equal.
    if (! (o instanceof Recipe)) {
      return false;
    }

    // The given object is a recipe - cast it.
    Recipe other = (Recipe) o;

    // Check index fields.
    if (! other.getRecipeName().equals(this.getRecipeName())) {
      // If the given recipe does not have the same name, it is not equal.
      return false;
    }
    else if (other.getId() != this.getId()) {
      // If the given recipe does not have the same ID, it is not equal.
      return false;
    }

    // Otherwise, the two recipes are equal.
    return true;
  }

  @Override
  public String toString() {
    return this.getRecipeName();
  }

  // Public methods
  public void update() {
    setColor(BrewCalculator.Color(this));
    setOG(BrewCalculator.OriginalGravity(this));
    setBitterness(BrewCalculator.Bitterness(this));
    setFG(BrewCalculator.FinalGravity(this));
    setABV(BrewCalculator.AlcoholByVolume(this));
    this.instructionGenerator.generate();
  }

  public String getRecipeName() {
    return this.name;
  }

  public void setRecipeName(String name) {
    this.name = name;
  }

  public void addIngredient(Ingredient i) {
    Log.d(getRecipeName() + "::addIngredient", "Adding ingredient: " + i.getName());
    if (i.getType().equals(Ingredient.HOP)) {
      addHop(i);
    }
    else if (i.getType().equals(Ingredient.FERMENTABLE)) {
      addFermentable(i);
    }
    else if (i.getType().equals(Ingredient.MISC)) {
      addMisc(i);
    }
    else if (i.getType().equals(Ingredient.YEAST)) {
      addYeast(i);
    }
    else if (i.getType().equals(Ingredient.WATER)) {
      addWater(i);
    }

    update();
  }

  public void removeIngredientWithId(long id) {
    for (Ingredient i : getIngredientList()) {
      if (i.getId() == id) {
        Log.d("Recipe", "Removing ingredient " + i.getName());
        removeIngredient(i);
      }
    }
  }

  public void removeIngredient(Ingredient i) {
    if (i.getType().equals(Ingredient.HOP)) {
      hops.remove(i);
    }
    else if (i.getType().equals(Ingredient.FERMENTABLE)) {
      fermentables.remove(i);
    }
    else if (i.getType().equals(Ingredient.MISC)) {
      miscs.remove(i);
    }
    else if (i.getType().equals(Ingredient.YEAST)) {
      yeasts.remove(i);
    }
    else if (i.getType().equals(Ingredient.WATER)) {
      waters.remove(i);
    }

    if (getIngredientList().contains(i)) {
      Log.d("Recipe", "Failed to remove ingredient");
    }
    else {
      Log.d("Recipe", "Successfully removed ingredient");
    }

    update();
  }

  public MashProfile getMashProfile() {
    return this.mashProfile;
  }

  public void setMashProfile(MashProfile profile) {
    this.mashProfile = profile;
    this.mashProfile.setRecipe(this);
    update();
  }

  public String getNotes() {
    return notes;
  }

  public void setNotes(String notes) {
    this.notes = notes;
  }

  public BeerStyle getStyle() {
    return style;
  }

  public void setStyle(BeerStyle beerStyle) {
    this.style = beerStyle;
  }

  public ArrayList<Ingredient> getIngredientList() {
    ArrayList<Ingredient> list = new ArrayList<Ingredient>();
    list.addAll(hops);
    list.addAll(fermentables);
    list.addAll(yeasts);
    list.addAll(miscs);
    list.addAll(waters);

    Collections.sort(list, new RecipeIngredientsComparator());
    return list;
  }

  public void setIngredientsList(ArrayList<Ingredient> ingredientsList) {

    for (Ingredient i : ingredientsList) {
      if (i.getType().equals(Ingredient.HOP)) {
        addHop(i);
      }
      else if (i.getType().equals(Ingredient.FERMENTABLE)) {
        addFermentable(i);
      }
      else if (i.getType().equals(Ingredient.MISC)) {
        addMisc(i);
      }
      else if (i.getType().equals(Ingredient.YEAST)) {
        addYeast(i);
      }
      else if (i.getType().equals(Ingredient.WATER)) {
        addWater(i);
      }
    }

    update();
  }

  private void addWater(Ingredient i) {
    Water w = (Water) i;
    waters.add(w);
  }

  private void addYeast(Ingredient i) {
    Yeast y = (Yeast) i;
    yeasts.add(y);
  }

  private void addMisc(Ingredient i) {
    Misc m = (Misc) i;
    miscs.add(m);
  }

  private void addFermentable(Ingredient i) {
    Log.d(getRecipeName() + "::addFermentable", "Adding fermentable: " + i.getName());
    Fermentable f = (Fermentable) i;
    this.fermentables.add(f);
  }

  private void addHop(Ingredient i) {
    Hop h = (Hop) i;
    this.hops.add(h);
  }

  public ArrayList<Instruction> getInstructionList() {
    return this.instructionGenerator.getInstructions();
  }

  public double getOG() {
    return OG;
  }

  public void setOG(double gravity) {
    gravity = (double) Math.round(gravity * 1000) / 1000;
    this.OG = gravity;
  }

  public double getBitterness() {
    bitterness = (double) Math.round(bitterness * 10) / 10;
    return bitterness;
  }

  public void setBitterness(double bitterness) {
    bitterness = (double) Math.round(bitterness * 10) / 10;
    this.bitterness = bitterness;
  }

  public double getColor() {
    color = (double) Math.round(color * 10) / 10;
    return color;
  }

  public void setColor(double color) {
    color = (double) Math.round(color * 10) / 10;
    this.color = color;
  }

  public double getABV() {
    ABV = (double) Math.round(ABV * 10) / 10;
    return ABV;
  }

  public void setABV(double aBV) {
    ABV = (double) Math.round(ABV * 10) / 10;
    ABV = aBV;
  }

  public int getBatchTime() {
    return batchTime;
  }

  public void setBatchTime(int batchTime) {
    this.batchTime = batchTime;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getVolumeUnits() {
    return Units.getVolumeUnits();
  }

  public double getDisplayBatchSize() {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.batchSize);
    }
    else {
      return this.batchSize;
    }
  }

  public void setDisplayBatchSize(double size) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.batchSize = Units.gallonsToLiters(size);
    }
    else {
      this.batchSize = size;
    }
  }

  public double getDisplayMeasuredBatchSize() {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      return Units.litersToGallons(this.measuredVol);
    }
    else {
      return this.measuredVol;
    }
  }

  public void setDisplayMeasuredBatchSize(double size) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.measuredVol = Units.gallonsToLiters(size);
    }
    else {
      this.measuredVol = size;
    }
  }

  public double getBeerXmlStandardBatchSize() {
    return this.batchSize;
  }

  public void setBeerXmlStandardBatchSize(double v) {
    this.batchSize = v;
  }

  public double getBeerXmlMeasuredBatchSize() {
    return this.measuredVol;
  }

  public void setBeerXmlMeasuredBatchSize(double d) {
    this.measuredVol = d;
  }

  public int getBoilTime() {
    return boilTime;
  }

  public void setBoilTime(int boilTime) {
    this.boilTime = boilTime;
  }

  public double getEfficiency() {
    if (this.getType().equals(EXTRACT)) {
      return 100;
    }
    return efficiency;
  }

  public void setEfficiency(double efficiency) {
    this.efficiency = efficiency;
  }

  public String getBrewer() {
    return brewer;
  }

  public void setBrewer(String brewer) {
    this.brewer = brewer;
  }

  public double getDisplayBoilSize() {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      if (this.calculateBoilVolume) {
        return Units.litersToGallons(calculateBoilVolume());
      }
      else {
        return Units.litersToGallons(this.boilSize);
      }
    }
    else {
      if (this.calculateBoilVolume) {
        return calculateBoilVolume();
      }
      else {
        return this.boilSize;
      }
    }
  }

  public void setDisplayBoilSize(double size) {
    if (Units.getVolumeUnits().equals(Units.GALLONS)) {
      this.boilSize = Units.gallonsToLiters(size);
    }
    else {
      this.boilSize = size;
    }
  }

  private double calculateBoilVolume() {
    // TODO: Get parameters from equipment profile.
    double TRUB_LOSS = Units.gallonsToLiters(.3);     // Liters lost
    double SHRINKAGE = .04;                           // Percent lost
    double EVAP_LOSS = Units.gallonsToLiters(1.5);    // Evaporation loss (L/hr)

    if (this.type.equals(Recipe.ALL_GRAIN)) {
      return batchSize * (1 + SHRINKAGE) + TRUB_LOSS + (EVAP_LOSS * Units.minutesToHours(boilTime));
    }
    else {
      return (batchSize / 3) * (1 + SHRINKAGE) + TRUB_LOSS + (EVAP_LOSS * Units.minutesToHours
              (boilTime));
    }
  }

  public double getBeerXmlStandardBoilSize() {
    return boilSize;
  }

  public void setBeerXmlStandardBoilSize(double boilSize) {
    this.boilSize = boilSize;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

  public double getFG() {
    this.FG = (double) Math.round(FG * 1000) / 1000;
    return this.FG;
  }

  public void setFG(double fG) {
    fG = (double) Math.round(fG * 1000) / 1000;
    this.FG = fG;
  }

  public int getFermentationStages() {
    return fermentationStages;
  }

  public void setFermentationStages(int fermentationStages) {
    this.fermentationStages = fermentationStages;
  }

  public int getTotalFermentationDays() {
    return this.primaryAge + this.secondaryAge + this.tertiaryAge;
  }

  public int getVersion() {
    return version;
  }

  public void setVersion(int version) {
    this.version = version;
  }

  public ArrayList<Misc> getMiscList() {
    return miscs;
  }

  public ArrayList<Fermentable> getFermentablesList() {
    return fermentables;
  }

  public ArrayList<Hop> getHopsList() {
    return hops;
  }

  public ArrayList<Yeast> getYeastsList() {
    return yeasts;
  }

  public ArrayList<Water> getWatersList() {
    return waters;
  }

  /**
   * Generates a list of hops in this recipe with the given use.
   *
   * @param use
   *         One of Ingredient.USE_*
   * @return An ArrayList of Ingredients.
   */
  public ArrayList<Ingredient> getHops(String use) {
    ArrayList<Ingredient> list = new ArrayList<Ingredient>();
    for (Hop h : this.getHopsList()) {
      if (h.getUse().equals(use)) {
        list.add(h);
      }
    }
    return list;
  }

  public double getMeasuredOG() {
    return this.measuredOG;
  }

  public void setMeasuredOG(double d) {
    this.measuredOG = d;
  }

  public double getMeasuredFG() {
    return this.measuredFG;
  }

  public void setMeasuredFG(double d) {
    this.measuredFG = d;
  }

  public int getDisplayCoolToFermentationTemp() {
    // Metric - imperial conversion is performed in Yeast
    for (Yeast y : this.getYeastsList()) {
      return y.getDisplayFermentationTemp();
    }
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return 65;
    }
    else {
      return (int) Units.fahrenheitToCelsius(65);
    }
  }

  public double getDisplaySteepTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(steepTemp);
    }
    else {
      return steepTemp;
    }
  }

  public void setDisplaySteepTemp(double t) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.steepTemp = Units.fahrenheitToCelsius(t);
    }
    else {
      this.steepTemp = t;
    }
  }

  public void setNumberFermentationStages(int stages) {
    this.fermentationStages = stages;
  }

  public void setFermentationAge(int stage, int age) {
    switch (stage) {
      case STAGE_PRIMARY:
        this.primaryAge = age;
      case STAGE_SECONDARY:
        this.secondaryAge = age;
      case STAGE_TERTIARY:
        this.tertiaryAge = age;
    }
  }

  public int getFermentationAge(int stage) {
    switch (stage) {
      case STAGE_PRIMARY:
        return this.primaryAge;
      case STAGE_SECONDARY:
        return this.secondaryAge;
      case STAGE_TERTIARY:
        return this.tertiaryAge;
      default:
        return 7;
    }
  }

  public void setBeerXmlStandardFermentationTemp(int stage, double temp) {
    switch (stage) {
      case STAGE_PRIMARY:
        this.primaryTemp = temp;
      case STAGE_SECONDARY:
        this.secondaryTemp = temp;
      case STAGE_TERTIARY:
        this.tertiaryTemp = temp;
    }
  }

  public double getBeerXmlStandardFermentationTemp(int stage) {
    switch (stage) {
      case STAGE_PRIMARY:
        return this.primaryTemp;
      case STAGE_SECONDARY:
        return this.secondaryTemp;
      case STAGE_TERTIARY:
        return this.tertiaryTemp;
      default:
        return 21;
    }
  }

  public void setDisplayFermentationTemp(int stage, double temp) {
    switch (stage) {
      case STAGE_PRIMARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          this.primaryTemp = Units.fahrenheitToCelsius(temp);
        }
        else {
          this.primaryTemp = temp;
        }
        break;

      case STAGE_SECONDARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          this.secondaryTemp = Units.fahrenheitToCelsius(temp);
        }
        else {
          this.secondaryTemp = temp;
        }
        break;

      case STAGE_TERTIARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          this.tertiaryTemp = Units.fahrenheitToCelsius(temp);
        }
        else {
          this.secondaryTemp = temp;
        }
        break;
    }
  }

  public double getDisplayFermentationTemp(int stage) {
    switch (stage) {
      case STAGE_PRIMARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(this.primaryTemp);
        }
        else {
          return this.primaryTemp;
        }

      case STAGE_SECONDARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(this.secondaryTemp);
        }
        else {
          return this.secondaryTemp;
        }

      case STAGE_TERTIARY:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(this.tertiaryTemp);
        }
        else {
          return this.tertiaryTemp;
        }

      default:
        if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
          return Units.celsiusToFahrenheit(21);
        }
        else {
          return 21;
        }
    }
  }

  public String getTasteNotes() {
    if (this.tasteNotes == null) {
      this.tasteNotes = "";
    }
    return this.tasteNotes;
  }

  public void setTasteNotes(String s) {
    this.tasteNotes = s;
  }

  public int getTasteRating() {
    return this.tasteRating;
  }

  public void setTasteRating(int i) {
    this.tasteRating = i;
  }

  public int getBottleAge() {
    return this.bottleAge;
  }

  public void setBottleAge(int i) {
    this.bottleAge = i;
  }

  public double getBeerXmlStandardBottleTemp() {
    return this.bottleTemp;
  }

  public void setBeerXmlStandardBottleTemp(double d) {
    this.bottleTemp = d;
  }

  public double getDisplayBottleTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.bottleTemp);
    }
    else {
      return this.bottleTemp;
    }
  }

  public void setDisplayBottleTemp(double d) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.bottleTemp = Units.fahrenheitToCelsius(d);
    }
    else {
      this.bottleTemp = d;
    }
  }

  public boolean isForceCarbonated() {
    return this.isForceCarbonated;
  }

  public void setIsForceCarbonated(boolean b) {
    this.isForceCarbonated = b;
  }

  public double getCarbonation() {
    return this.carbonation;
  }

  public void setCarbonation(double d) {
    this.carbonation = d;
  }

  public void setLastModified(String s) {
    this.lastModified = s;
  }

  public Date getLastModified() {
    try {
      Date d = new SimpleDateFormat(Constants.LAST_MODIFIED_DATE_FMT).parse(this.lastModified);
      Log.d("Recipe", this.toString() + " was last modified " + d.toString());
      return d;
    } catch (ParseException e) {
      Log.w("Recipe", "Failed to parse lastModified: " + this.lastModified);
      return new Date();
    } catch (NullPointerException e) {
      // No modified date was ever set - likely a recipe from before this
      // feature existed.
      Log.w("Recipe", "No last modified for recipe: " + this.toString());
      return new Date();
    }
  }

  public String getBrewDate() {
    if (this.brewDate == null) {
      this.brewDate = "";
    }
    return this.brewDate;
  }

  public void setBrewDate(String s) {
    this.brewDate = s;
  }

  /* There is no standardized date format in beerXML.  Thus, we need
   * to try and parse as many formats as possible.  This method takes the given raw
   * date string and returns the best effort Date object.  If not we're unable to parse
   * the date, then this method returns today's date.
   */
  public Date getBrewDateDate() {
    // First, try the common date formats to speed things up.  We'll resort to a full search
    // of known formats if these fail.
    String d = this.getBrewDate();

    // This format is common for BeerSmith recipes.
    try {
      return new SimpleDateFormat("MM/dd/yyyy").parse(d);
    } catch (ParseException e) {
      // Do nothing.
    }

    // This format is used by Biermacht.
    try {
      return new SimpleDateFormat("dd MMM yyyy").parse(d);
    } catch (ParseException e) {
      // Do nothing.
    }

    // This takes a long time, so only do it as a last resort.
    // Look through a bunch of known formats to figure out what it is.
    String fmt = DateUtil.determineDateFormat(d);
    if (fmt == null) {
      Log.w("Recipe", "Failed to parse date: " + d);
      return new Date();
    }
    try {
      return new SimpleDateFormat(fmt).parse(d);
    } catch (ParseException e) {
      Log.e("Recipe", "Failed to parse date: " + d);
      e.printStackTrace();
      return new Date();
    }
  }

  public String getPrimingSugarName() {
    return this.primingSugarName;
  }

  public void setPrimingSugarName(String s) {
    this.primingSugarName = s;
  }

  public double getPrimingSugarEquiv() {
    // TODO
    return 0.0;
  }

  public void setPrimingSugarEquiv(double d) {
    this.primingSugarEquiv = d;
  }

  public double getBeerXmlStandardCarbonationTemp() {
    return this.carbonationTemp;
  }

  public void setBeerXmlStandardCarbonationTemp(double d) {
    this.carbonationTemp = d;
  }

  public double getDisplayCarbonationTemp() {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      return Units.celsiusToFahrenheit(this.carbonationTemp);
    }
    else {
      return this.carbonationTemp;
    }
  }

  public void setDisplayCarbonationTemp(double d) {
    if (Units.getTemperatureUnits().equals(Units.FAHRENHEIT)) {
      this.carbonationTemp = Units.fahrenheitToCelsius(d);
    }
    else {
      this.carbonationTemp = d;
    }
  }

  public double getKegPrimingFactor() {
    return this.kegPrimingFactor;
  }

  public void setKegPrimingFactor(double d) {
    this.kegPrimingFactor = d;
  }

  public int getCalories() {
    return this.calories;
  }

  public void setCalories(int i) {
    this.calories = i;
  }

  public boolean getCalculateBoilVolume() {
    return this.calculateBoilVolume;
  }

  public void setCalculateBoilVolume(boolean b) {
    this.calculateBoilVolume = b;
  }

  public boolean getCalculateStrikeVolume() {
    return this.calculateStrikeVolume;
  }

  public void setCalculateStrikeVolume(boolean b) {
    this.calculateStrikeVolume = b;
  }

  public boolean getCalculateStrikeTemp() {
    return this.calculateStrikeTemp;
  }

  public void setCalculateStrikeTemp(boolean b) {
    this.calculateStrikeTemp = b;
  }

  public double getMeasuredABV() {
    if (this.getMeasuredFG() > 0 && this.getMeasuredOG() > this.getMeasuredFG()) {
      return (this.getMeasuredOG() - this.getMeasuredFG()) * 131;
    }
    else {
      return 0;
    }
  }

  public double getMeasuredEfficiency() {
    double potGravP, measGravP;
    double eff = 100;
    double measBatchSize = this.getBeerXmlMeasuredBatchSize();

    // If the user hasn't input a measured batch size, assume the recipe went as planned
    // and that the target final batch size was hit.
    if (measBatchSize == 0) {
      Log.d("Recipe", "No measured batch size, try using recipe batch size");
      measBatchSize = this.getBeerXmlStandardBatchSize();
    }

    if (! this.getType().equals(Recipe.EXTRACT)) {
      eff = getEfficiency();
    }

    // Computation only valid if measured gravity is greater than 1, and batch size is non-zero.
    // Theoretically, measured gravity could be less than 1, but we don't support that yet.
    if ((this.getMeasuredOG() > 1) && (batchSize > 0)) {
      // Calculate potential milli-gravity points.  Adjust the value returned by the
      // brew calculator, because it takes the expected efficiency into account (which
      // we don't want here).
      potGravP = (BrewCalculator.OriginalGravity(this) - 1) / (eff / 100);

      // Adjust potential gravity points to account for measured batch size.
      potGravP = potGravP * (getBeerXmlStandardBatchSize() / measBatchSize);

      // Calculate the measured milli-gravity points.
      measGravP = this.getMeasuredOG() - 1;

      // Return the efficiency.
      return 100 * measGravP / potGravP;
    }
    else {
      return 0;
    }
  }

  public void save(Context c) {
    Log.d(getRecipeName() + "::save", "Saving with id: " + this.getId());
    new DatabaseAPI(c).updateRecipe(this);
  }
} public class Stack {
  private ArrayList<String> list;

  public Stack() {
    list = new ArrayList<String>();
  }

  public void push(String s) {
    Log.d("Stack", "Pushing '" + s + "' on the stack");
    list.add(s);
  }

  public String pop() {
    String s = list.get(list.size() - 1);
    list.remove(s);
    Log.d("Stack", "Popping '" + s + "' off of the stack");
    return s;
  }

  public String read() {
    try {
      return list.get(list.size() - 1);
    } catch (IndexOutOfBoundsException e) {
      // Index out of bounds - there are no items in the stack.
      // Return null to indicate the stack is empty, but don't raise
      // the Exception.
      return null;
    }

  }

  public boolean contains(String s) {
    return list.contains(s);
  }
} public class Units {

  // Unit systems
  public static final String IMPERIAL = "Imperial";
  public static final String METRIC = "Metric";

  // Imperial Units
  public static final String OUNCES = "oz";
  public static final String GALLONS = "gal";
  public static final String POUNDS = "lbs";
  public static final String TEASPOONS = "tsp";
  public static final String FAHRENHEIT = "\u2109";
  public static final String CUP = "Cup";
  public static final String CUPS = "Cups";
  public static final String QUARTS_PER_POUND = "qt/lb";
  public static final String QUARTS = "qt";
  public static final String PINTS = "pt";
  public static final String TABLESPOONS = "tbps";

  // Metric Units
  public static final String KILOGRAMS = "kg";
  public static final String GRAMS = "grams";
  public static final String MILLIGRAMS = "mg";
  public static final String LITERS = "liters";
  public static final String MILLILITERS = "ml";
  public static final String CELSIUS = "\u2103";
  public static final String LITERS_PER_KG = "L/kg";

  // Agnostic Units
  public static final String PACKAGES = "pkg";
  public static final String ITEMS = "items";
  public static final String DAYS = "days";
  public static final String MINUTES = "mins";
  public static final String HOURS = "hours";
  public static final String PLATO = "plato";
  public static final String GRAVITY = "sg";
  public static final String UNITS = "units";

  // Formal units - Full spellings for displaying in lists, etc.
  // Make sure to add these to the converter below!
  public static final String TEASPOONS_FORMAL = "Teaspoons";
  public static final String CUPS_FORMAL = "Cups";
  public static final String GRAMS_FORMAL = "Grams";
  public static final String GALLONS_FORMAL = "Gallons";
  public static final String LITERS_FORMAL = "Liters";
  public static final String OUNCES_FORMAL = "Ounces";
  public static final String ITEMS_FORMAL = "Items";
  public static final String KILOGRAMS_FORMAL = "Kilograms";
  public static final String MILLILITERS_FORMAL = "Milliliters";
  public static final String PACKAGES_FORMAL = "Packages";
  public static final String POUNDS_FORMAL = "Pounds";
  public static final String UNITS_FORMAL = "Units";

  // SharedPreferences to use.
  private static SharedPreferences preferences = BiermachtApplication.getContext().getSharedPreferences(Constants.PREFERENCES, Context.MODE_PRIVATE);

  public static String toAbbreviation(String s) {
    if (s.toLowerCase().equals(TEASPOONS_FORMAL.toLowerCase())) {
      return TEASPOONS;
    }
    if (s.toLowerCase().equals(CUPS_FORMAL.toLowerCase())) {
      return CUPS;
    }
    if (s.toLowerCase().equals(GRAMS_FORMAL.toLowerCase())) {
      return GRAMS;
    }
    if (s.toLowerCase().equals(GALLONS_FORMAL.toLowerCase())) {
      return GALLONS;
    }
    if (s.toLowerCase().equals(LITERS_FORMAL.toLowerCase())) {
      return LITERS;
    }
    if (s.toLowerCase().equals(OUNCES_FORMAL.toLowerCase())) {
      return OUNCES;
    }
    if (s.toLowerCase().equals(ITEMS_FORMAL.toLowerCase())) {
      return ITEMS;
    }
    if (s.toLowerCase().equals(KILOGRAMS_FORMAL.toLowerCase())) {
      return KILOGRAMS;
    }
    if (s.toLowerCase().equals(MILLILITERS_FORMAL.toLowerCase())) {
      return MILLILITERS;
    }
    if (s.toLowerCase().equals(PACKAGES_FORMAL.toLowerCase())) {
      return PACKAGES;
    }
    if (s.toLowerCase().equals(POUNDS_FORMAL.toLowerCase())) {
      return POUNDS;
    }
    else {
      return UNITS;
    }
  }

  public static String toFormal(String s) {
    if (s.toLowerCase().equals(TEASPOONS.toLowerCase())) {
      return TEASPOONS_FORMAL;
    }
    if (s.toLowerCase().equals(CUPS.toLowerCase())) {
      return CUPS_FORMAL;
    }
    if (s.toLowerCase().equals(GRAMS.toLowerCase())) {
      return GRAMS_FORMAL;
    }
    if (s.toLowerCase().equals(GALLONS.toLowerCase())) {
      return GALLONS_FORMAL;
    }
    if (s.toLowerCase().equals(LITERS.toLowerCase())) {
      return LITERS_FORMAL;
    }
    if (s.toLowerCase().equals(OUNCES.toLowerCase())) {
      return OUNCES_FORMAL;
    }
    if (s.toLowerCase().equals(ITEMS.toLowerCase())) {
      return ITEMS_FORMAL;
    }
    if (s.toLowerCase().equals(KILOGRAMS.toLowerCase())) {
      return KILOGRAMS_FORMAL;
    }
    if (s.toLowerCase().equals(MILLILITERS.toLowerCase())) {
      return MILLILITERS_FORMAL;
    }
    if (s.toLowerCase().equals(PACKAGES.toLowerCase())) {
      return PACKAGES_FORMAL;
    }
    if (s.toLowerCase().equals(POUNDS.toLowerCase())) {
      return POUNDS_FORMAL;
    }
    else {
      return UNITS_FORMAL;
    }
  }

  public static String getUnitsFromDisplayAmount(String s) {
    Log.d("Units", "Getting units from display amount: " + s);
    String unit = "";
    ArrayList<String> temp = new ArrayList<String>(Arrays.asList(s.split(" ")));
    if (temp.size() == 2) {
      unit = temp.get(temp.size() - 1);
    }

    // Assign a Units class standard unit here
    if (unit.equals("tsp") || unit.equals("teaspoons")) {
      unit = Units.TEASPOONS;
    }
    if (unit.equalsIgnoreCase("qt") || unit.equalsIgnoreCase("quarts") || unit.equalsIgnoreCase
            ("quart") || unit.equalsIgnoreCase("qts")) {
      unit = Units.QUARTS;
    }
    if (unit.equals("g") || unit.equals("grams")) {
      unit = Units.GRAMS;
    }
    if (unit.equals("oz") || unit.equals("ounces")) {
      unit = Units.OUNCES;
    }
    if (unit.contains("gal")) {
      unit = Units.GALLONS;
    }
    if (unit.equals("item") || unit.equals("items")) {
      unit = Units.ITEMS;
    }
    if (unit.contains("package") || unit.equals("pkg")) {
      unit = Units.PACKAGES;
    }
    if (unit.equalsIgnoreCase(PLATO)) {
      unit = Units.PLATO;
    }
    if (unit.equalsIgnoreCase(GRAVITY)) {
      unit = Units.GRAVITY;
    }
    if (unit.equalsIgnoreCase("F")) {
      unit = Units.FAHRENHEIT;
    }
    if (unit.equalsIgnoreCase("C")) {
      unit = Units.CELSIUS;
    }
    if (unit.equalsIgnoreCase("qt/lb")) {
      unit = Units.QUARTS_PER_POUND;
    }
    if (unit.equalsIgnoreCase(LITERS_PER_KG)) {
      unit = Units.LITERS_PER_KG;
    }

    Log.d("Units", "Got units: " + unit);
    return unit;
  }

  public static double getAmountFromDisplayAmount(String s) {
    Log.d("Units", "Getting amount from display amount: " + s);
    ArrayList<String> temp = new ArrayList<String>(Arrays.asList(s.split(" ")));
    if (getUnitsFromDisplayAmount(s).equals("")) {
      Log.d("Units", "Unable to determine units from: " + s);
      return 0;
    }
    else if (temp.size() <= 2 && temp.size() > 0) {
      try {
        Log.d("Units", "Returning amount: " + Double.parseDouble(temp.get(0).replace(",", ".")));
        return Double.parseDouble(temp.get(0).replace(",", "."));
      } catch (Exception e) {
        e.printStackTrace();
        Log.d("Units", "Error parsing display amount: " + s);
      }
    }
    else {
      Log.d("Units", "Received bad display amount: " + s);
    }
    Log.d("Units", "Failed to get amount. Returning 0");
    return 0;
  }

  public static int toSeconds(double time, String units) {
    if (units.equals(MINUTES)) {
      return (int) (time * 60);
    }
    if (units.equals(HOURS)) {
      return (int) (time * 3600);
    }

    return 0;
  }

  // Functions for converting units below
  public static double platoToGravity(double p) {
    return p / (258.6 - ((p / 258.2) * 227.1)) + 1;
  }

  public static double fahrenheitToCelsius(double f) {
    return (f - 32) / 1.8;
  }

  public static double celsiusToFahrenheit(double c) {
    return c * 1.8 + 32;
  }

  public static double LPKGtoQPLB(double d) {
    return d * .479305709;
  }

  public static double QPLBtoLPKG(double d) {
    return d / .479305709;
  }

  public static double litersToGallons(double l) {
    return l * .264172052;
  }

  public static double cupsToLiters(double c) {
    return c * .236;
  }

  public static double litersToCups(double l) {
    return l / .236;
  }

  public static double litersToMillis(double l) {
    return l * 1000;
  }

  public static double millisToLiters(double m) {
    return m / 1000;
  }

  public static double minutesToDays(double m) {
    return m / 1440;
  }

  public static double minutesToHours(double m) {
    return m / 60;
  }

  public static double daysToMinutes(double d) {
    return d * 1440;
  }

  public static double gallonsToLiters(double g) {
    return g / .264172052;
  }

  public static double litersToOunces(double l) {
    return l / .0295735;
  }

  public static double ouncesToLiters(double o) {
    return o * .0295735;
  }

  public static double litersToTeaspoons(double l) {
    return l * 202.884;
  }

  public static double litersToTablespoons(double l) {
    return l * 67.628;
  }

  public static double teaspoonsToLiters(double t) {
    return t / 202.884;
  }

  public static double tablespoonsToLiters(double t) {
    return t / 67.628;
  }

  public static double quartsToLiters(double q) {
    return q * .946353;
  }

  public static double pintsToLiters(double p) {
    return p / 2.11338;
  }

  public static double litersToQuarts(double l) {
    return l / .946353;
  }

  public static double litersToPints(double l) {
    return 1 / 2.11338;
  }

  public static double kilosToPounds(double k) {
    return k * 2.204;
  }

  public static double kilosToOunces(double k) {
    return k * 35.2739619;
  }

  public static double ouncesToKilos(double o) {
    return o / 35.2739619;
  }

  public static double poundsToKilos(double p) {
    return p / 2.204;
  }

  public static double kilosToGrams(double k) {
    return k * 1000;
  }

  public static double gramsToKilos(double g) {
    return g / 1000;
  }

  public static double milligramsToKilos(double mg) {
    return mg / 1000 / 1000;
  }

  public static double kilosToMilligrams(double kg) {
    return kg * 1000 * 1000;
  }

  public static double ouncesToGrams(double o) {
    return o * 28.3495231;
  }

  public static double gramsToOunces(double g) {
    return g / 28.3495231;
  }

  // Methods to return the units for each measurement system
  public static String getUnitSystem() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return IMPERIAL;
    }
    else {
      return METRIC;
    }
  }

  public static String getHopUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return OUNCES;
    }
    else {
      return GRAMS;
    }
  }

  public static String getFermentableUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return POUNDS;
    }
    else {
      return KILOGRAMS;
    }
  }

  public static String getWaterToGrainUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return QUARTS_PER_POUND;
    }
    else {
      return LITERS_PER_KG;
    }
  }

  public static String getStrikeVolumeUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return QUARTS;
    }
    else {
      return LITERS;
    }
  }

  public static String getTemperatureUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return FAHRENHEIT;
    }
    else {
      return CELSIUS;
    }
  }

  public static String getVolumeUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals(IMPERIAL)) {
      return GALLONS;
    }
    else {
      return LITERS;
    }
  }

  public static String getWeightUnits() {
    if (preferences.getString(Constants.PREF_MEAS_SYSTEM, Units.IMPERIAL).equals
            (IMPERIAL)) {
      return POUNDS;
    }
    else {
      return KILOGRAMS;
    }
  }

  public static double toLiters(double amount, String unit) {
    if (unit.equals(GALLONS)) {
      return gallonsToLiters(amount);
    }
    if (unit.equals(QUARTS)) {
      return quartsToLiters(amount);
    }
    if (unit.equals(MILLILITERS)) {
      return millisToLiters(amount);
    }
    if (unit.equals(CUPS) || unit.equals(CUP)) {
      return cupsToLiters(amount);
    }
    if (unit.equals(TEASPOONS)) {
      return teaspoonsToLiters(amount);
    }
    if (unit.equals(OUNCES)) {
      return ouncesToLiters(amount);
    }
    if (unit.equals(LITERS)) {
      return amount;
    }
    return - 1;
  }

  public static String getMetricEquivalent(String imp, boolean isWeight) {
    if (imp.equals(Units.GALLONS)) {
      return LITERS;
    }
    else if (imp.equals(Units.TEASPOONS)) {
      return MILLILITERS;
    }
    else if (imp.equals(Units.OUNCES)) {
      if (isWeight) {
        return GRAMS;
      }
      else {
        return MILLILITERS;
      }
    }
    else if (imp.equals(Units.POUNDS)) {
      return KILOGRAMS;
    }
    else if (imp.equals(Units.CUP) || imp.equals(Units.CUPS)) {
      return LITERS;
    }
    else {
      return imp;
    }
  }
}
import android.util.Log;
import com.biermacht.brews.ingredient.Fermentable;
import com.biermacht.brews.ingredient.Hop;
import com.biermacht.brews.ingredient.Ingredient;
import com.biermacht.brews.ingredient.Misc;
import com.biermacht.brews.ingredient.Yeast;
import com.biermacht.brews.recipe.BeerStyle;
import com.biermacht.brews.recipe.MashProfile;
import com.biermacht.brews.recipe.MashStep;
import com.biermacht.brews.recipe.Recipe;
import com.biermacht.brews.utils.Stack;
import com.biermacht.brews.utils.Units;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
        }
      }

      r.addIngredient(f);
      return;
    }
    else if (qName.equalsIgnoreCase("HOPS"))
    // Finished a hop.  Add to recipe and list
    {
      thingTypeStack.pop();
      r.addIngredient(h);
      hopList.add(h);
      return;
    }
    else if (qName.equalsIgnoreCase("YEAST"))
    // Finished a yeast. Add to recipe and list
    {
      thingTypeStack.pop();
      r.addIngredient(y);
      yeastList.add(y);
      return;
    }
    else if (qName.equalsIgnoreCase("MISC"))
    // Finished a misc.  Add to recipe and list
    {
      thingTypeStack.pop();
      r.addIngredient(misc);
      miscList.add(misc);
      return;
    }
    else if (qName.equalsIgnoreCase("F_R_STYLE"))
    // Finished a style.  Add to recipe and list
    {
      thingTypeStack.pop();
      beerStyleList.add(style);
      r.setStyle(style);
      return;
    }
    else if (qName.equalsIgnoreCase("MashStep"))
    // Finished a mash step, add to list and profile
    {
      thingTypeStack.pop();
      mashStepList.add(mashStep);
      profile.addMashStep(mashStep);
      return;
    }
    else if (qName.equalsIgnoreCase("F_R_MASH"))
    // Finished a mash profile. Add to recipe and list
    {
      thingTypeStack.pop();
      r.setMashProfile(profile);
      mashProfileList.add(profile);
      return;
    }

    else if (qName.equalsIgnoreCase("EQUIPMENT")) {
      thingTypeStack.pop();
      return;
    }

    /************************************************************
     * Handle individual types of ingredients / things below.  We check
     * the "thingType" and base our actions accordingly.
     ************************************************************/

    // First, make sure that the stack actually has something in it.
    // If the stack is empty, it likely means the file contains some tags
    // we don't support.  In this case, just return and wait until we find
    // some XML tags we do support.
    if (thingTypeStack.read() == null) {
      Log.w("BsmxXmlReader", "Got element '" + qName + "', but no corresponding opening element.  Skip");
      return;
    }

    if (thingTypeStack.read().equalsIgnoreCase("Recipe") ||
            thingTypeStack.read().equalsIgnoreCase("F_R_EQUIPMENT") ||
            thingTypeStack.read().equalsIgnoreCase("F_R_AGE")) {
      // TODO: Some fields that are part of "Recipe" in Biermacht are part of "Equipment" and "Age" in BSMX, so we group them together.
      if (qName.equalsIgnoreCase("F_R_NAME")) {
        r.setRecipeName(currentValue);
      }

      else if (qName.equalsIgnoreCase("F_R_VERSION")) {
        // TODO: Support this field.
      }

      else if (qName.equalsIgnoreCase("F_R_TYPE")) {
        String type;

        if (Integer.parseInt(currentValue) == 0) {
          type = Recipe.EXTRACT;
        }
        else if (Integer.parseInt(currentValue) == 1) {
          type = Recipe.PARTIAL_MASH;
        }
        else {
          type = Recipe.ALL_GRAIN;
        }

        r.setType(type);
      }

      else if (qName.equalsIgnoreCase("F_R_NOTES")) {
        r.setNotes(currentValue);
      }

      else if (qName.equalsIgnoreCase("F_R_DATE")) {
        r.setBrewDate(currentValue);
      }

      else if (qName.equalsIgnoreCase("F_R_BREWER")) {
        r.setBrewer(currentValue);
      }

      else if (qName.equalsIgnoreCase("F_R_ASST_BREWER")) {
        // TODO
      }

      else if (qName.equalsIgnoreCase("F_E_BATCH_VOL")) {
        // .bsmx uses ounces for volume, so convert to liters first.
        double s = Units.ouncesToLiters(Float.parseFloat(currentValue));