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

#include "Repository.h"
#include "resources.h"

#include <qfileinfo.h>
#include <qstring.h>
#include <iostream>
#include <sstream>

/*! Instanciate the singleton instance. */
Repository* Repository::m_instance = 0;

/*! Singleton access. */
Repository*
Repository::getInstance()
{
   if (m_instance == 0)
      m_instance = new Repository();
   return m_instance;
}

/*! Delete the singleton instance. Destructor is protected for a reason. */
void
Repository::deleteInstance() {
   if (m_instance != 0) {
      delete m_instance;
      m_instance = 0;
   }
}

/*! 
  Constructs an empty repository. The list of available measures is 
  initialised with the abbreviations defined by Meal Master.
 */
Repository::Repository()
{
   m_cookbooks.resize( 9973);
   m_cookbooks.setAutoDelete( true);
   m_activeMangoFormatVersion = MANGO_FILE_VERSION_CURRENT;
}

/*! Destructor. */
Repository::~Repository()
{
}

/*! Read in an existing cookbook.
  The format is not yet known and needs to be determined.
  \see addCookbookFromFile( const QString&, RecipeModel::RecipeFormat)
  \todo If no extension matches, try to determine the format by actually
        looking at the file contents.
*/
RecipeModel*
Repository::addCookbookFromFile( const QString& fname)
   throw (Error)
{
   RecipeModel* rm = 0;
   QFileInfo finfo( fname);
   QString ext = finfo.extension( false);
   if (ext.lower() == "mango")
      rm = addCookbookFromFile( fname, RecipeModel::Mango);
   else if (ext.lower() == "cml")
      rm = addCookbookFromFile( fname, RecipeModel::CookML);
   else if (ext.lower() == "recipeml" || ext.lower() == "xml")
      rm = addCookbookFromFile( fname, RecipeModel::RecipeML);
   else if ((ext.lower() == "mm") || (ext.lower() == "mmf"))
      rm = addCookbookFromFile( fname, RecipeModel::MealMaster);
   else if (ext.lower() == "rk")
      rm = addCookbookFromFile( fname, RecipeModel::Rezkonv);
   else
      throw( Error( tr( "Unknown file type. Reading file aborted.")));
   
   setDefaultPath( "LoadDirectory", finfo.dirPath());
   return rm;
}

/*!
  Reads in an existing cookbook.
  
  @param filename Name of file containing cookbook to add to repository.
  @param format Format of the file to open. Allowed formats are declared in 
         class RecipeModel.
*/
RecipeModel*
Repository::addCookbookFromFile( const QString& fname,
                                 RecipeModel::RecipeFormat format)
{
   RecipeModel* rm = new RecipeModel( fname, format);
   if (!rm->isEmpty()) insertCookbook( rm);
   return rm;
}

/*!
  Inserts the cookbook into the repository and associates it
  with its name.
  \param rm the cookbook to be inserted into the repository.
*/
void
Repository::insertCookbook( const RecipeModel* rm)
{
   m_cookbooks.insert( rm->getName(), rm);
   emit cookbookAdded( rm->getName());
}

/*!
  Creates a new (and <em>empty</em>) cookbook.
  
  \param name Name associated with the cookbook.
*/
void
Repository::createCookbook( const QString& name)
{
   RecipeModel* rm = new RecipeModel( name);
   m_cookbooks.insert( name, rm);
   emit cookbookAdded( name);
}

/*!
  Remove the cookbook with the associated \c name from the repository.
  \param deleteAlso if true, the cookbook is really deleted, if not the
         cookbook stays valid but is not associated with a name in here
         anymore. Useful for renaming.
*/
void
Repository::removeCookbook( const QString& name, bool deleteAlso)
{
   if (deleteAlso)
      m_cookbooks.remove( name);
   else
      m_cookbooks.take( name);
}

/*!
  Return the cookbook associated with the given name. In case the book
  does not already exist, an exception is thrown.
 
  \param name Name of the cookbook that should be returned.
  \param format Format of file to be opened.
  \returns Pointer to cookbook associated with the given name.
*/
RecipeModel*
Repository::getCookbook( const QString& name) const
   throw (Error)
{
   if (m_cookbooks.isEmpty()) {
      QString s( tr( "The internal cookbook list is empty"));
      throw Error( s);
   }

   RecipeModel* ret = m_cookbooks.find( name);
   if (!ret) {
      QString s( tr( "Cookbook %1 is not available in the internal repository.") .arg( name));
      throw Error( s);
   }

   return ret;
} // getCookbook()

/*!
  \return list of all cookbooks currently known.
*/
Repository::Cookbooks
Repository::getAllCookbooks() const
{
   return m_cookbooks;
}

/*! Return the number of cookbooks stored. */
int
Repository::getNumOfCookbooks() const
{
   return m_cookbooks.size();
}

/*!
  \return Names of all cookbooks.
*/
QStringList
Repository::getCookbookNames() const
{
   QStringList ret;
   CookbooksIterator it( m_cookbooks);
   for (; it.current(); ++it)
      ret << it.currentKey();

   return ret;
} // allCookbookNames()

/*!
  \return File names for all cookbooks.
*/
QStringList
Repository::getCookbookFileNames() const
{
   QStringList ret;
   CookbooksIterator it( m_cookbooks);
   for (; it.current(); ++it)
      ret << it.current()->getFileName();
   return ret;
} // getCookbookFileNames()

/*! Returns whether or not a cookbook associated with the given name exists. */
bool
Repository::hasCookbook( const QString& name) const
{
   bool ret = false;
   RecipeModel* rm = m_cookbooks.find( name);
   if (rm) ret = true;
   return ret;
}

/*!
  Returns true in case at least one cookbook has unsaved changes,
  false otherwise.
*/
bool
Repository::hasUnsavedCookbooks() const
{
   bool rc = false;
   CookbooksIterator it( m_cookbooks);
   for (; it.current(); ++it) {
      if (it.current()->isModified()) {
         rc = true;
         break;
      }
   }
   return rc;
}

/*!
  Traverse all stored cookbooks. In case one has unsaved changes, save them.
*/
void
Repository::saveAllChanges()
{
   CookbooksIterator it( m_cookbooks);
   for (; it.current(); ++it) {
      RecipeModel* current = it.current();
      if (current->isModified())
         current->saveToFile();
   }
} // saveAllChanges()

/*!
  Add the given category to the categories list. Each category is stored
  only once.
*/
void
Repository::addCategory( const QString& name)
{
   bool changed = false;
   Recipe::Categories::iterator it = m_categories.begin();
   while (it != m_categories.end()) {
	  if ((*it) == name)
		 break;
	  it++;
   }
   if (it == m_categories.end()) {
	  m_categories.insert( m_categories.end(), name);
	  changed = true;
   }
   if (changed) m_categories.sort();
}

/*! Return all currently known categories. */
Recipe::Categories 
Repository::getKnownCategories() const
{
   return m_categories;
}

void
Repository::setCurrentRecipe( const Recipe& r) // public
{
   m_currentRecipe = r;
}

Recipe
Repository::currentRecipe() const // public
{
   return m_currentRecipe;
}

/*!
  This directory is used as starting point to look for new recipes, cookbooks,
  or what not.

  Therefore, it is probably a good idea to use this function whenever the
  user changes this directory in a file dialog.
*/
bool
Repository::setCurrentDir( QDir& dir)
{
   bool rc = QDir::setCurrent( dir.path());
   return rc;
}

/*!
  Use this function for setting defaults in file dialogs et cetera.
*/
QDir
Repository::getCurrentDir() const
{
   return QDir::current();
}

/*!
  Currently known keys: ExportDirectory, LoadDirectory.

  \return the default path associated with \a key.
  \see setDefaultPath()
*/
QString
Repository::defaultPath( const QString& key)
{
   return m_defaultPaths[key];
}

/*!
  Store \a path and associate it with \a key.

  \see defaultPath()
*/
void
Repository::setDefaultPath( const QString& key, const QString& path)
{
   m_defaultPaths[key] = path;
}

Q_INT32 Repository::
activeMangoFormatVersion() const
{
   return m_activeMangoFormatVersion;
}

void Repository::
setActiveMangoFormatVersion( const Q_INT32 v)
{
   m_activeMangoFormatVersion = v;
}
