/* -*- 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: CMLImportHandler.cpp,v 1.2 2003/06/18 19:41:55 rjesse Exp $
 */

#include "CMLImportHandler.h"
#include "RecipeModel.h"

/*!
  Constructs an instance of the class.
*/
CMLImportHandler::CMLImportHandler( RecipeModel* rm)
   : m_model( rm)
{
}

/*!
  This function is invoked whenever there is an element to be parsed.
  \param qName is the qualified name of the current element to parse.
  \param atts are the attributes of the element. If there are no attributes, it is empty.
*/
bool CMLImportHandler::
startElement( const QString&, const QString&, const QString& qName, const QXmlAttributes& atts)
{
   QString s;
   m_curElement.truncate( 0);
   if ("cookml" == qName) {
       QString name( atts.value( "name"));
       if (!name.isEmpty()) m_model->setName( name);
        // just ignore version, prog, and progver
   }
   else if ("recipe" == qName) {
       m_curRecipe.clear();
       QString lang( atts.value( "lang"));
       if (!lang.isEmpty()) m_curRecipe.setLang( lang);
   } // recipe
   else if ("head" == qName) {
       s = atts.value( "title");
       m_curRecipe.setTitle( s);
       s = atts.value( "servingqty");
       m_curRecipe.setYieldQty( s.toInt());
       s = atts.value( "servingtype");
       m_curRecipe.setYieldUnit( s);
       s = atts.value( "createuser");
       m_curRecipe.setTypist( s);
       s = atts.value( "createdate");
       if (! s.isEmpty())
          m_curRecipe.setCreationDate( extractDateTime( s));
       s = atts.value( "createemail");
       m_curRecipe.setTypistEmail( s);
       s = atts.value( "changeuser");
       m_curRecipe.setModificationUser( s);
       s = atts.value( "changedate");
       if (! s.isEmpty())
          m_curRecipe.setModificationDate( extractDateTime( s));
       s = atts.value( "changeemail");
       m_curRecipe.setModificationEmail( s);
       s = atts.value( "timeprepqty");
       m_curRecipe.setPreparationTime( "preparation", QTime( 0, s.toInt()));
       s = atts.value( "timecookqty");
       m_curRecipe.setPreparationTime( "cooking", QTime( 0, s.toInt()));
       s = atts.value( "timeallqty");
       m_curRecipe.setPreparationTime( "overall", QTime( 0, s.toInt()));
       s = atts.value( "costs");
       m_curRecipe.setCosts( s);
       s = atts.value( "wwpoints");
       bool rc = false;
       float wwpoints = s.toFloat( &rc);
       if (rc)
          m_curRecipe.setWwPoints( wwpoints);
       s = atts.value( "rid");
       m_curRecipe.setFingerprint( s);
   } // recipe : head
   else if (("cat" == qName)    // recipe : (cat, hint, sourceline)
            || ("hint" == qName)
            || ("sourceline" == qName))
   {
      m_curElement = qName;
   }
   else if ("content" == qName) { // recipe : content
      QString s1 = atts.value( "type");
      QString s2 = atts.value( "value");
      m_curRecipe.addBlsContent( s1, s2);
   } // recipe : content
   else if ("picbin" == qName) { // recipe : picbin
      m_curElement = qName;
   }
   else if ("picture" == qName) { // recipe : picture
      s = atts.value( "file");
      if (! s.isEmpty()) {
         m_curRecipe.setPictureUri( m_curRecipe.title(), s);
      } // <picture file="http://...."/>
   } // recipe : picture
   else if ("custom" == qName) { // recipe : custom (the trash container)
      QString name = atts.value( "name");
      QString datatype = atts.value( "datatype");
      QString value = atts.value( "value");
      m_curRecipe.addCustomData( name, datatype, value);
   } // recipe : custom
   else if ("part" == qName) {  // recipe : part (list of ingredients)
      s = atts.value( "title");
      if (s.isEmpty())
         m_curIngredientGroupName.truncate( 0);
      else
         m_curIngredientGroupName = s;
   } // recipe : part
   else if ("ingredient" == qName) { // recipe : part : ingredient
      if (! m_curIngredient.isEmpty()) {
         // the previous ingredient is done.
         addCurIng2Recipe();
      }
      m_curIngredient.clear();
      m_curElement = qName;
      
      s = atts.value( "qty");
      if (! s.isEmpty()) m_curIngredient.setAmountQty( s.toFloat());
      s = atts.value( "unit");
      if (! s.isEmpty()) {
         Measurement m( s);
         m_curIngredient.setMeasurement( m);
      }
      s = atts.value( "item");
      if (! s.isEmpty()) m_curIngredient.setName( s);
      s = atts.value( "bls");
      if (! s.isEmpty()) m_curIngredient.setBls( s);
      
      s = atts.value( "calc");
      if ("false" == s.lower()) m_curIngredient.setCalc( false);
      if ("true" == s.lower()) m_curIngredient.setCalc( true);

      s = atts.value( "gram");
      if (! s.isEmpty()) m_curIngredient.setGram( s.toInt());

      s = atts.value( "shop");
      if ("false" == s.lower()) m_curIngredient.setShopping( false);
      if ("true" == s.lower()) m_curIngredient.setShopping( true);

      s = atts.value( "ridlink");
      if (! s.isEmpty()) m_curIngredient.setRecipeId( s);
   } // recipe : part : ingredient
   else if ("inote" == qName) { // recipe : part : ingredient : inote
      m_curElement = qName;
   } // recipe : part : ingredient : inote
   else if ("step" == qName)    // recipe : preparation : step
      m_curElement = qName;
   else if ("text" == qName)    // recipe : preparation : text
      m_curElement = qName;
   else if ("remark" == qName) { // recipe : remark
      s = atts.value( "user");
      m_curRemarkUserName = s;
   } // recipe : remark
   else if ("line" == qName)
      m_curElement = qName;
   else if ("menu" == qName) {
      m_curMenu.clear();
      s = atts.value( "title");
      m_curMenu.setTitle( s);
   }
   else if ("mcustom" == qName) { // menu : mcustom
      QString s1 = atts.value( "name");
      QString s2 = atts.value( "value");
      m_curMenu.addCustomData( s1, s2);
   }
   else if ("mrecipe" == qName) { // menu : mrecipe
      m_curMenuRecipe.clear();
      s = atts.value( "amount");
      m_curMenuRecipe.setAmount( s.toInt());
      s = atts.value( "persons");
      m_curMenuRecipe.setPersons( s.toInt());
      s = atts.value( "rid");
      m_curMenuRecipe.setRecipeId( s);
      s = atts.value( "title");
      // ignore recipe's title for now, may be used in case the above id is invalid
   }
   else if ("desc" == qName) {   // menu : mrecipe : desc
      m_curElement = qName;
   }

   return true;
}

/*!
  This function is called whenever a closing element was found.
  \param qName the qualified name of the closed element.
*/
bool
CMLImportHandler::endElement( const QString&, const QString&, const QString& qName)
{
   if ("cat" == qName)          // recipe : cat*
      m_curRecipe.addCategory( m_data[qName]);
   else if ("hint" == qName)    // recipe : hint*
      m_curRecipe.addKeywords( m_data[qName]);
   else if ("sourceline" == qName) { // recipe : sourceline*
      QString s( m_curRecipe.source());
      if (! s.isEmpty()) s.append( " ");
      s.append( m_data[qName]);
      m_curRecipe.setSource( s);
   }
   else if ("inote" == qName)   // recipe : part : ingredient : inote
      m_curIngredient.setAnnotation( m_data[qName]);
   else if ("ingredient" == qName) { // recipe : part : ingredient
      addCurIng2Recipe();
   }
   else if ("part" == qName) {  // recipe : part
      if (! m_curIngredient.isEmpty()) {
         addCurIng2Recipe();
      }
   }
   else if ("picbin" == qName) { // recipe : picbin
      QPixmap pic = Recipe::decodeBase64( m_data[qName]);
      if (pic.isNull())
         m_curRecipe.setDefaultPicture();
      else {
         if (! m_curImageDesc.isEmpty())
            m_curRecipe.setPicture( m_curImageDesc, pic);
         else
            m_curRecipe.setPicture( m_curRecipe.title(), pic);
      }
      m_curImageDesc.truncate( 0);
   }
   else if ("step" == qName)    // recipe : preparation : step
      m_curRecipe.add2Directions( m_data[qName]);
   else if ("text" == qName)    // recipe : preparation : text
      m_curRecipe.add2Directions( m_data[qName]);
   else if ("remark" == qName) { // recipe : remark
      m_curRecipe.setRemarksForUser( m_curRemarkUserName, m_curRemarkLines);
      m_curRemarkUserName.truncate( 0);
      m_curRemarkLines.clear();
   }
   else if ("line" == qName)    // recipe : remark : line
      m_curRemarkLines.append( m_data[qName]);
   else if ("recipe" == qName)  // recipe
      m_model->add( m_curRecipe);
   else if ("mrecipe" == qName) // menu : mrecipe
      m_curMenu.addMenuRecipe( m_curMenuRecipe);
   else if ("menu" == qName)    // menu
      m_model->addMenu( m_curMenu);
   else if ("desc" == qName) {  // {picbin,mrecipe} : desc
      switch (m_curDescElement) {
      case DESC_PICBIN:
         m_curImageDesc.append( m_data[qName]);
         break;
      case DESC_MRECIPE:
         m_curMenuRecipe.setDescription( m_data[qName]);
         break;
      default:
         QString s( QObject::tr( "Internal error in CMLImportHandler: found \"desc\" element that does not belong to anybody."));
         qDebug( s.ascii());
         break;
      }
      m_curDescElement = NO_DESC_ELEMENT;
   }

   return true;
}

/*!
  Helper function that adds the currently parsed ingredient to
  the current recipe. This also resets the current ingredient afterwards.
*/
void
CMLImportHandler::addCurIng2Recipe()
{
   QString group;
   if (m_curIngredientGroupName.isEmpty()) group == QString::null;
   else group = m_curIngredientGroupName;
   m_curRecipe.addIngredient( m_curIngredient, group);
   m_curIngredient.clear();
}

QDateTime
CMLImportHandler::extractDateTime( const QString& dtStr) const
{
   QDateTime ret;
   int midPos = dtStr.find( "T");
   if (midPos == -1)
      midPos = dtStr.length();
   QString dateStr( dtStr.left( midPos));
   QString s( dateStr.left( 4));
   int year = s.toInt();
   s = dateStr.mid( 5, 2);
   int month = s.toInt();
   s = dateStr.right( 2);
   int day = s.toInt();
   ret.setDate( QDate( year, month, day));
   QString timeStr( dtStr.right( dtStr.length() - midPos - 1));
   QTime time = QTime::fromString( timeStr);
   ret.setTime( time);
   return ret;
}

/*!
  Reads data of the current element and stores it in m_data.
*/
bool
CMLImportHandler::characters( const QString& ch)
{
   m_data[m_curElement] += ch;
   return true;
}
