/* -*- indent-tabs-mode: nil; -*-
 *
 * This file is part of the Mango recipe management distribution.
 * Copyright (c) 2000-2003, Roland Jesse
 * All rights reserved.
 *
 * For license and copyright information see the file COPYRIGHT or 
 * <http://www.opensource.org/licenses/bsd-license.html>.
 *
 * $Id: Ingredient.cpp,v 1.7 2003/05/11 14:45:02 rjesse Exp $
 */

#include "Ingredient.h"
#include "AmbrosiaFingerprint.h"
#include "Repository.h"
#include <qdatastream.h>

struct IngredientData {
      long refCount;
      QString name;             //!< The name of the ingredient (i.e. "Chili").
      QString annotation;       //!< Additional note for the ingredient (i.e. "red hot").
      float amountQty;            //!< (minimum) amount.
      float amountRange;          //!< (maximum) range.
      Measurement measurement; //!< Measurement name.
      Q_UINT32 gram;            //!< \internal How much gram make up the recipe.
      QString bls;              //!< The German "Bundeslebensmittelschlüssel".
      bool doCalc;              //!< Flag whether or not this ingredient add to the calculation of a recipe's "food value."
      float wwPoints;           //!< Weight Watchers points.
      bool doShopping; //!< Include this ingredient on shopping lists.
      AmbrosiaFingerprint recipeId; //!< Id of a recipe that forms this ingredient.
};

Ingredient::Ingredient()
{
   d = new IngredientData();
   d->refCount = 1;
   clear();
}

Ingredient::Ingredient( const Ingredient& other)
{
   d = other.d;
   d->refCount++;
}

Ingredient::~Ingredient()
{
   if (--d->refCount == 0)
      delete d;
}

Ingredient&
Ingredient::operator=( const Ingredient& right)
{
   right.d->refCount++;
   if (--d->refCount == 0)
      delete d;
   d = right.d;
   return *this;
}

void
Ingredient::detach()
{
   if (d->refCount > 1) {
      d->refCount--;
      d = new IngredientData();
      d->refCount = 1;
   }
}

bool
Ingredient::operator==( const Ingredient& right) const
{
   bool rc = true;
   if (d->name != right.name()) rc = false;
   else if (d->annotation != right.annotation()) rc = false;
   else if (d->amountQty != right.amountQty()) rc = false;
   else if (d->amountRange != right.amountRange()) rc = false;
   else if (d->measurement != right.measurement()) rc = false;
   return rc;
}

/*!
  All that is currently added, is the name, i.e. amount
  and measurements are not handled.

  \todo Change: If measurement is the same -> add up the amount, too.
*/
Ingredient&
Ingredient::operator+=( const Ingredient& right) // public
{
   if (this == &right) { /* self-assignment */ }
   detach();
   d->name.append( right.name());
   return *this;
}

void
Ingredient::clear()				// public
{
   detach();
   d->amountQty = 0.0;
   d->amountRange = 0.0;
   Measurement m;
   d->measurement = m;
   d->name.truncate(0);
   d->annotation.truncate(0);
}

/*
  We don't care whether amount or measurement are set. Without a non empty
  name the ingredient is empty.
*/
bool
Ingredient::isEmpty() const      // public
{
   bool rc = d->name.isEmpty();
   return rc;
}

/*!
  \return the name of the ingredient (i.e. "Chili").
  \sa setName()
  \sa annotation()
*/
QString
Ingredient::name() const
{
   return d->name;
}

/*!
  Set the name of the ingredient (i.e. "Chili").  
  \sa name()
  \sa setAnnotation()
*/
void
Ingredient::setName( const QString& s)
{
   detach();
   d->name = s;
}

/*!
  \return the annotation for the ingredient (i.e. "red hot").
*/
QString Ingredient::
annotation() const
{
   return d->annotation;
}

/*!
  \sa annotation()
  \sa setName()
*/
void Ingredient::
setAnnotation( const QString& s)
{
   detach();
   d->annotation = s;
}

float Ingredient::
amountQty() const
{
   return d->amountQty;
}

void Ingredient::
setAmountQty( const float f)
{
   detach();
   d->amountQty = f;
}

float Ingredient::
amountRange() const
{
   return d->amountRange;
}

void Ingredient::
setAmountRange( const float f)
{
   detach();
   d->amountRange = f;
}

Measurement
Ingredient::measurement() const
{
   return d->measurement;
}

void
Ingredient::setMeasurement( const Measurement& m)
{
   detach();
   d->measurement = m;
}

bool
Ingredient::scalable() const
{
   return d->measurement.isScaleable();
}

/*!
  \return how much grams make up this ingredient.
  
  Unlikely to be used intensively. Good for CookML compatibility and BLS support.
  \sa bls()
*/
Q_UINT32 Ingredient::
gram() const
{
   return d->gram;
}

/*!
  Set how much gram make up this ingredient.

  This is more or less for internal use and support of the BLS.
*/
void Ingredient::
setGram( const Q_UINT32 g)
{
   detach();
   d->gram = g;
}

/*!
  \return the German Bundeslebensmittelschlüssel.
*/
QString Ingredient::
bls() const
{
   return d->bls;
}

/*!
  Set the key of the German Bundeslebensmittelschlüssel.
*/
void Ingredient::
setBls( const QString& s)
{
   detach();
   d->bls = s;
}

/*!  \return whether or not this ingredient takes part in the
  calculation of a recipe's "food value."
*/
bool Ingredient::
doCalc() const
{
   return d->doCalc;
}

/*! Set whether or not this ingredient is supposed to be part of the
 calculation of a recipe's "food value."
*/
void Ingredient::
setCalc( const bool b)
{
   detach();
   d->doCalc = b;
}

/*!
  \return Weight Watchers points for this ingredient.
  \sa setWwPoints()
*/
float Ingredient::
wwPoints() const
{
   return d->wwPoints;
}

/*!
  Set the Weight Watchers points for this ingredient.
  \sa wwPoints()
*/
void Ingredient::
setWwPoints( const float f)
{
   detach();
   d->wwPoints = f;
}

/*!
  \return the id of a recipe that forms this ingredient.
*/
QString Ingredient::
recipeId() const
{
   return d->recipeId.asString();
}

/*!
  Set the id of a recipe that actually forms this ingredient.
*/
void Ingredient::
setRecipeId( const QString& s)
{
   detach();
   d->recipeId = AmbrosiaFingerprint( s);
}

/*!
  \return whether or not to put this ingredient on a shopping list.
*/
bool Ingredient::
doShopping() const
{
   return d->doShopping;
}

/*!
  Set whether or not to put this ingredient on a shopping list.
*/
void Ingredient::
setShopping( const bool b)
{
   detach();
   d->doShopping = b;
}

/*****************************************************************************
  Ingredient stream functions
 *****************************************************************************/

/*!
  \relates Ingredient
  Writes the ingredient \a ing to the stream \a s.
*/
QDataStream&
operator<<( QDataStream& s, const Ingredient& ing)
{
   s << ing.name();
   s << ing.annotation();       // since: version 2
   s << ing.amountQty();
   s << ing.amountRange();      // since: version 2
   s << ing.measurement().mmAbbrev();
   s << ing.measurement().isScaleable();
   s << ing.gram();             // since: version 2
   s << ing.bls();              // since: version 2
   s << ing.doCalc();           // since: version 2
   s << ing.wwPoints();         // since: version 2
   s << ing.doShopping();       // since: version 2
   s << ing.recipeId();         // since: version 2
   return s;
}

/*!
  \relates Ingredient
  Reads an ingredient from the stream \a s into ingredient \a ing.
*/
QDataStream&
operator>>( QDataStream& s, Ingredient& ing)
{
   Repository* repo = Repository::getInstance();
   QString str;
   s >> str;
   ing.setName( str);
   if (repo->activeMangoFormatVersion() >= 2) {
      s >> str;
      ing.setAnnotation( str);
   }   
   
   if (repo->activeMangoFormatVersion() == 1) {
      s >> str;
      ing.setAmountQty( str.toFloat());
   }
   else {                       // format version 2 and above.
      float f;
      s >> f;
      ing.setAmountQty( f);
      s >> f;
      ing.setAmountRange( f);
   }
   s >> str;
   Measurement m( str);
   ing.setMeasurement( m);
   
   /*
     Read in scale information even though that is currently only
     determined by the measurement. Might be useable later. Ignore it
     for now.
   */
   bool b;
   s >> b;

   if (repo->activeMangoFormatVersion() >= 2) {
      Q_UINT32 i;
      s >> i;
      ing.setGram( i);
      
      s >> str;
      ing.setBls( str);

      bool b;
      s >> b;
      ing.setCalc( b);
      
      float f;
      s >> f;
      ing.setWwPoints( f);

      s >> b;
      ing.setShopping( b);

      s >> str;
      ing.setRecipeId( str);
   }

   return s;
}

/*!
  Helper function because QDataStream does not know about bool by default.
*/
QDataStream&
operator<<( QDataStream& s, const bool& b)
{
   const Q_INT8 val = b;
   s << val;
   return s;
}

/*!
  Helper function because QDataStream does not know about bool by default.
*/
QDataStream&
operator>>( QDataStream& s, bool& b)
{
   Q_INT8 val;
   s >> val;
   b = val;
   return s;
}
