/* -*- 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: RecipeModel.cpp,v 1.45 2003/05/09 17:23:38 rjesse Exp $
 */

#include "RecipeModel.h"
#include "Repository.h"
#include "Mango.h"
#include "MangoExporter.h"
#include "MangoImporter.h"
#include "CMLImporter.h"
#include "Menu.h"
#include "MMImporter.h"
#include "RecipeMLImporter.h"
#include "RKImporter.h"

#include <qfiledialog.h>
#include <qmessagebox.h>
#include <qprogressdialog.h>

/*! Constructs an empty cookbook. */
RecipeModel::RecipeModel( bool isTemp)
   : QObject(), m_modified( false), m_isTemp( isTemp), m_importer(0)
{
}

/*! Copy constructor. */
RecipeModel::RecipeModel( const RecipeModel& rm, bool isTemp)
   : QObject(), m_name( rm.m_name), m_recipes(rm.m_recipes), m_menus( rm.m_menus), m_modified( rm.m_modified), m_isTemp( isTemp), m_importer(0)
{
}

/*! Constructs an empty cookbook. */
RecipeModel::RecipeModel( const QString& name, bool isTemp)
   : m_name( name), m_modified( false), m_isTemp( isTemp), m_importer(0)
{
}

/*!
  Constructs an cookbook which contains all recipes contained in the given
  file.
*/
RecipeModel::RecipeModel( const QString& fname, const RecipeFormat format, bool isTemp)
   : m_modified( false), m_isTemp( isTemp)
{
   m_name = "No name set, yet";

   switch (format) {
   case Mango: {
      try {
         loadMangoFile( fname);
      }
      catch (Error& ex) {
         /* see MealMaster comment below. */
         throw ex;
      }
   }
      break;
   case CookML: {
      try {
         loadCookMLFile( fname);
      }
      catch (Error& e) {
         // see MealMaster comment below.
         throw e;
      }
   }
      break; 
   case MealMaster: {
      try {
         loadMMFile( fname);
      }
      catch (Error& ex) {
         /*
          * in case the file either doesn't exist or does not contain
          * anything, the cookbook stays empty therefore:
          * don't handle it here.
          */
         throw ex;
      }
   }
      break;		 
   case RecipeML: {
      try {
         loadRecipeMLFile( fname);
      }
      catch (Error& ex) {
         /* see comment above */
         QString str( tr( "Error reading file as XML: "));
         str.append( ex.what());
         QMessageBox::warning( Mango::getInstance(), tr( "Reading file"), str);
      }
   }
      break;
   case Rezkonv: {
      try {
         loadRKFile( fname);
      }
      catch (Error& e) {
         /* see comment above. */
         QString s( tr( "Error reading file %1 as Rezkonv: %2")
                    .arg( fname) .arg( e.what()));
         QMessageBox::warning( Mango::getInstance(), tr( "Reading file"), s);
      }
   }
      break;
   default:
      QMessageBox::warning( Mango::getInstance(),
                            tr( "Reading file"),
                            tr( "Error: Unknown file format!"));
   }
} // constructor

RecipeModel::~RecipeModel()
{
}

RecipeModel&
RecipeModel::operator=( const RecipeModel& right) // public
{
   if (this == &right) return *this;
   m_name = right.m_name;
   m_filename = right.m_filename;
   m_recipes = right.m_recipes;
   m_menus = right.m_menus;
   m_modified = true;
   return *this;
}						  

/*!
  Append all recipes contained in the given cookbook to this cookbook. There is
  no verification whether or not a recipe in the given cookbook is already part
  of this cookbook. We might end up having double entries. Mail Roland in case
  that annoys the hell out of you. :)
*/
RecipeModel&
RecipeModel::operator+=( RecipeModel& right) // public
{
   if (this == &right) { /* self-assignment */ }
   RecipeList::iterator it = right.m_recipes.begin();
   while (it != right.m_recipes.end()) {
	  add( *it);
	  it++;
   }
   MenuList::iterator mIt = right.m_menus.begin();
   while (mIt != right.m_menus.end()) {
      m_menus.append( *mIt);
      mIt++;
   }
   m_modified = true;
   return *this;
}

/*!
  Append a recipe to the cookbook.

  \param r non-const as its internal cookbook list is modified by add().
*/
RecipeModel&
RecipeModel::operator+=( Recipe& r) // public
{
   Recipe tmp( r);
   add( tmp);
   m_modified = true;
   return *this;
}

QString
RecipeModel::getName() const	// public
{
   return m_name;
}

void
RecipeModel::setName( const QString& name) // public
{
   QString oldName( m_name);
   m_name = name;
   
   // FIXME: remove legacy:
//    if (!oldName.isEmpty()) {
//       // Update entry in the Repository.
//       Repository::getInstance()->insertCookbook( this);
//    }
   m_isTemp = false;
   m_modified = true;
   emit notify();
}

QString
RecipeModel::getFileName() const
{
   return m_filename;
}

void
RecipeModel::setFileName( const QString& fname)
{
   m_filename = fname;
   m_modified = false;
}

/*!
  Returns whether or not the cookbook is empty.

  An cookbook is empty in case it does not (yet) contain any recipes.
*/
bool
RecipeModel::isEmpty() const	// public
{
   return m_recipes.empty();
}

/*!
  Returns whether or not the cookbook contains the recipe \a r.
*/
bool RecipeModel::
contains( const Recipe& r) const
{
   bool found = false;
   RecipeList::const_iterator it = m_recipes.begin();
   while (it != m_recipes.end()) {
      if ((*it) == r) {
         found = true;
         break;
      }
      it++;
   }
   return found;
}

/*!
  Return whether or not there are unsaved changes to this cookbook.
*/
bool
RecipeModel::isModified() const // public
{
   bool rc = false;
   if (!m_isTemp && m_modified) rc = true;
   return rc;
}

void
RecipeModel::markAsModified( bool modified)
{
   m_modified = modified;
}

/*!
  \param r the recipe to be added. Non-const as the name of this
  cookbook is added to the recipe's internal cookbook list.
*/
void
RecipeModel::add( Recipe& r)	// public
{
   r.addCookbook( m_name);
   m_recipes.push_back( r);
   m_modified = true;
   emit notify();
}

/*!
  \param r the recipe to be removed.
*/
void
RecipeModel::remove( const Recipe& r)
{
   bool removed = false;
   RecipeList::iterator it;
   for (it = m_recipes.begin(); it != m_recipes.end(); it++) {
      if ((*it).title() == r.title()) {
         m_recipes.erase( it);
         removed = true;
         break;
      }
   }
   if (removed) {
      m_modified = true;
      emit notify();
   }
} // remove()

/*!
  Mark the recipe \a r as changed. This will result in the signal notify()
  to be emitted.
*/
void
RecipeModel::update( const Recipe& r)
{
   bool found = false;
   RecipeList::iterator it;
   for (it = m_recipes.begin(); it != m_recipes.end(); it++) {
      if ((*it).title() == r.title()) {
         found = true;
         *it = r;
         markAsModified( true);
         break;
      }
   }
   if (found) emit notify();
} // update()

RecipeModel::RecipeList
RecipeModel::getRecipeList() const // public
{
   return m_recipes;
}

/*!
  Reads in recipe(s) from the specified file. The file is expected to
  be in %Mango's native file format.

  \bug Assign to *this not before deleteImporter() has been called (?!)
  
  \param fname name of file to read.
*/
void
RecipeModel::loadMangoFile( const QString& fname)
   throw (Error)
{
   m_importer = new MangoImporter();
   setupImporterProgress();
   m_importer->setFileName( fname);
   try {
      *this = m_importer->apply();
   }
   catch (Error& e) {
      throw e;
   }
   deleteImporter();
   m_modified = false;
}
   
//! Reads in recipe(s) from the specified file.
/*!
  The File is expected to be in Meal-Master format.
  
  \param fname name of file to read.
*/
void
RecipeModel::loadMMFile( const QString& fname)
   throw (Error)
{
   m_importer = new MMImporter();
   setupImporterProgress();
   m_importer->setFileName( fname);
   try {
	  *this = m_importer->apply();
   }
   catch (Error& e) {
	  throw e;
   }
   deleteImporter();
   m_modified = false;
} // loadMMFile()

//! Reads in recipe(s) from the specified file. 
/*!
  The file is expected to be in RecipeML format.

  \param fname name of file to read.
*/
void
RecipeModel::loadRecipeMLFile( const QString& fname)
   throw (Error)
{
   m_importer = new RecipeMLImporter();
   setupImporterProgress();
   m_importer->setFileName( fname);
   try {
	  *this = m_importer->apply();
   }
   catch (Error& e) {
	  throw e;
   }
   deleteImporter();
   m_modified = false;
} // loadRecipeMLFile()

/*!
  Reads in recipe(s) from the specified file.
  The file is expected to be in Rezkonv format.
  \param fname name of file to read.
*/
void
RecipeModel::loadRKFile( const QString& fname)
   throw (Error)
{
   m_importer = new RKImporter();
   setupImporterProgress();
   m_importer->setFileName( fname);
   try {
      *this = m_importer->apply();
   }
   catch (Error& e) {
      throw( e);
   }
   deleteImporter();
   m_modified = false;
}

/*!
  Reads in recipe(s) from the specified file.
  The file is expected to be in CookML format.
  \param fname of of file to read.
*/
void
RecipeModel::loadCookMLFile( const QString& fname)
   throw (Error)
{
   m_importer = new CMLImporter();
   setupImporterProgress();
   m_importer->setFileName( fname);
   try {
      *this = m_importer->apply();
   }
   catch (Error& e) {
      throw( e);
   }
   deleteImporter();
   m_modified = true;
}

/*!
  Return recipe with the given title.

  \bug Recipe* ret = 0 invalidates any possibly set picture.
*/
Recipe
RecipeModel::findByTitle( const QString& title) // public
   throw (Error)
{
   Recipe r;
   bool found = false;
   RecipeList::const_iterator it = m_recipes.begin();
   while (it != m_recipes.end()) {
	  if ((*it).title() == title) {
             found = true;
             r = *it;
             break;
	  }
	  it++;
   }
   if (! found) {
      QString es( tr( "Recipe with title %1 not found.") .arg( title));
      throw Error( es);
   }
   return r;
}

/*!
  Saves the Cookbook to a file in Mango's native file format.
*/
void
RecipeModel::saveToFile()
   throw (Error)
{
   MangoExporter* exporter = new MangoExporter( this);
   if (m_filename.isEmpty()) {
      QString ext = exporter->getExtensions();
      // FIXME: next line returns a null string!?
      m_filename = QFileDialog::getSaveFileName( getName(), ext);
   }
   exporter->setFileName( m_filename);
   try {
      exporter->apply();
      m_modified = false;
   }
   catch (Error& e) {
      throw e;                  // pass it on
   }
   delete exporter;
} // saveToFile()

/*!
  An (file) import started. Thight might possibly require to open up
  a progress dialog.
*/
void
RecipeModel::slotImporterStarted( const QString& descr, const int total)
{
   m_progressImport = new QProgressDialog( descr, tr( "Cancel"), total, Mango::getInstance(), "Import progress dialog", true);
   m_progressImport->setCaption( tr( "Reading recipe data...", "Caption of the progress bar dialog."));
   connect( m_progressImport, SIGNAL( cancelled()), SLOT( slotCancelImport()));
} // slotImporterStarted()

/*!
  An (file) import reported some progress. Update the progress dialog
  accordingly.
*/
void
RecipeModel::slotImporterProgress( const int current, const int total)
{
   if (m_progressImport->totalSteps() != total)
      m_progressImport->setTotalSteps( total);
   m_progressImport->setProgress( current);
} // slotImporterProgress()

/*!
  Set up all necessary signal/slot connections needed for the reporting
  importer progress to the user.
*/
void
RecipeModel::setupImporterProgress()
{
   if (!m_importer)
      return;
   
   connect( m_importer, SIGNAL( start( const QString&, const int)), SLOT( slotImporterStarted( const QString&, const int)));
   connect( m_importer, SIGNAL( progress( const int, const int)), SLOT( slotImporterProgress( const int, const int)));
} // setupImporterProgress()

/*!
  The user wants us to stop reading a (large) file.
*/
void
RecipeModel::slotCancelImport()
{
   if (m_importer) {
      m_importer->stop();
      delete m_importer;
      m_importer = 0;
   }
} // slotCancelImport()

/*!
  In case m_importer points to a valid Importer object, delete it.
*/
void
RecipeModel::deleteImporter()
{
   if (m_importer) {
      delete m_importer;
      m_importer = 0;
   }
} // deleteImporter()

/*!
   \return all known menus.
*/
RecipeModel::MenuList 
RecipeModel::getMenuList() const
{
   return m_menus;
}

/*!
   Add \a menu to the list of menus.
*/
void 
RecipeModel::addMenu( const Menu& menu)
{
   m_menus.append( menu);
}
