/***************************************************************************
*   Copyright (C) 2003                                                    *
*   Unai Garro (ugarro@users.sourceforge.net)                             *
*   Cyril Bosselut (bosselut@b1project.com)                               *
*                                                                         *
*   Copyright (C) 2003-2006 Jason Kivlighn (jkivlighn@gmail.com)          *
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
***************************************************************************/

#ifndef RECIPEDB_H
#define RECIPEDB_H

#include <tqobject.h>
#include <tqstring.h>
#include <tqvaluelist.h>

#include <dcopclient.h>

#include "krecipesdbiface.h"

#include "datablocks/recipe.h"
#include "datablocks/recipelist.h"
#include "datablocks/elementlist.h"
#include "datablocks/ingredientpropertylist.h"
#include "datablocks/unitratiolist.h"
#include "datablocks/unit.h"

#define DEFAULT_DB_NAME "Krecipes"

/**
@author Unai Garro
*/

class TDEProcess;
class TQTextStream;

class CategoryTree;
class RecipeSearchParameters;
class Weight;
class WeightList;

typedef struct
{
	TQValueList <int> recipeIdList;
	IngredientList ilist;
}
RecipeIngredientList;

class RecipeDB: public TQObject, virtual public KrecipesDBIface
{
	TQ_OBJECT

public:
	RecipeDB();
	virtual ~RecipeDB();

	virtual void connect( bool create_db = true, bool create_tables = true ) = 0;

	void importSamples();

	bool backup( const TQString &file ){ return backup(file,0); }
	bool backup( const TQString &file, TQString *errMsg = 0 );
	bool restore( const TQString &file, TQString *errMsg = 0 );

	// Error handling (passive)
	bool dbOK;
	TQString dbErr;

	enum RecipeItems { 
		None = 0,
		NamesOnly = 256,
		Noatime = 1024,
		Photo = 1,
		Instructions = 2,
		Ingredients = 4,
		Authors = 8,
		Categories = 16,
		PrepTime = 32,
		Yield = 64,
		Title = 128,
		Meta = 512,
		Ratings = 2048,
		Properties = 4096,
		IngredientAmounts = 8192,
		All = 0xFFFF ^ NamesOnly ^ Noatime
	};

	enum ConversionStatus {
		Success,
		MissingUnitConversion,
		MissingIngredientWeight,
		MissingIngredient,
		InvalidTypes,
		MismatchedPrepMethod,
		MismatchedPrepMethodUsingApprox
	};

public slots:
	void cancelOperation(){ haltOperation = true; }

signals:
	void progressBegin(int,const TQString &c=TQString::null,const TQString &t=TQString::null,int rate=1);
	void progressDone();
	void progress();

	void authorCreated( const Element & );
	void authorRemoved( int id );

	void categoryCreated( const Element &, int parent_id );
	void categoryRemoved( int id );
	void categoryModified( const Element & );
	void categoryModified( int id, int parent_id );
	void categoriesMerged( int id1, int id2 );

	void ingGroupCreated( const Element & );
	void ingGroupRemoved( int id );

	void ingredientCreated( const Element & );
	void ingredientRemoved( int id );

	void prepMethodCreated( const Element & );
	void prepMethodRemoved( int id );

	void propertyCreated( const IngredientProperty & );
	void propertyRemoved( int id );

	void unitCreated( const Unit & );
	void unitRemoved( int id );

	void ratingCriteriaCreated( const Element & );

	void recipeCreated( const Element &, const ElementList &categories );
	void recipeRemoved( int id );
	void recipeRemoved( int id, int cat_id );
	void recipeModified( const Element &, const ElementList &categories );

	// Public methods
public:
	/** Returns a database object of the given type or NULL upon failure.
	  * This function should be called to create a new database, rather
	  * than directly calling the constructor of a specific backend.
	  */
	static RecipeDB* createDatabase( const TQString &dbType,
	                                 const TQString &host,
	                                 const TQString &user,
	                                 const TQString &pass,
	                                 const TQString &DBname,
	                                 int port,
	                                 const TQString &file = TQString::null );

	/** Convenience method.  Calls the above with arguments from TDEConfig. */
	static RecipeDB* createDatabase( const TQString &dbType, const TQString &file = TQString::null );

	virtual void addIngredientWeight( const Weight & ) = 0;
	virtual void addProperty( const TQString &name, const TQString &units ) = 0;
	virtual void addPropertyToIngredient( int ingredientID, int propertyID, double amount, int perUnitsID ) = 0;
	virtual void addUnitToIngredient( int ingredientID, int unitID ) = 0;

	virtual void categorizeRecipe( int recipeID, const ElementList &categoryList ) = 0;
	virtual void changePropertyAmountToIngredient( int ingredientID, int propertyID, double amount, int per_units ) = 0;

	virtual void createNewAuthor( const TQString &authorName ) = 0;
	virtual void createNewCategory( const TQString &categoryName, int parent_id = -1 ) = 0;
	virtual void createNewIngGroup( const TQString &name ) = 0;
	virtual void createNewIngredient( const TQString &ingredientName ) = 0;
	virtual void createNewPrepMethod( const TQString &prepMethodName ) = 0;
	virtual void createNewRating( const TQString &name ) = 0;
	virtual void createNewUnit( const Unit &unit ) = 0;
	virtual void createNewYieldType( const TQString &type ) = 0;

	virtual void emptyData( void ) = 0;
	virtual void empty( void ) = 0;

	virtual int findExistingAuthorByName( const TQString& name ) = 0;
	virtual int findExistingCategoryByName( const TQString& name ) = 0;
	virtual int findExistingIngredientGroupByName( const TQString& name ) = 0;
	virtual int findExistingIngredientByName( const TQString& name ) = 0;
	virtual int findExistingPrepByName( const TQString& name ) = 0;
	virtual int findExistingPropertyByName( const TQString& name ) = 0;
	virtual int findExistingRatingByName( const TQString& name ) = 0;
	virtual int findExistingRecipeByName( const TQString& name ) = 0;
	virtual int findExistingUnitByName( const TQString& name ) = 0;
	virtual int findExistingYieldTypeByName( const TQString& name ) = 0;
	virtual void findIngredientUnitDependancies( int ingredientID, int unitID, ElementList *recipes, ElementList *ingredientInfo ) = 0;
	virtual void findIngredientDependancies( int ingredientID, ElementList *recipes ) = 0;
	virtual void findPrepMethodDependancies( int prepMethodID, ElementList *recipes ) = 0;
	virtual void findUnitDependancies( int unitID, ElementList *properties, ElementList *recipes, ElementList *weights ) = 0;
	virtual void findUseOfIngGroupInRecipes( ElementList *results, int groupID ) = 0;
	virtual void findUseOfCategoryInRecipes( ElementList *results, int catID ) = 0;
	virtual void findUseOfAuthorInRecipes( ElementList *results, int authorID ) = 0;

	void getIDList( const CategoryTree *categoryTree, TQStringList &ids );
	virtual TQString getUniqueRecipeTitle( const TQString &recipe_title ) = 0;
	virtual void givePermissions( const TQString &dbName, const TQString &username, const TQString &password = TQString::null, const TQString &clientHost = "localhost" ) = 0;

	void importUSDADatabase();

	virtual bool ingredientContainsProperty( int ingredientID, int propertyID, int perUnitsID ) = 0;
	virtual bool ingredientContainsUnit( int ingredientID, int unitID ) = 0;

	void initializeData( void );

	virtual int lastInsertID() = 0;

	virtual void loadAuthors( ElementList *list, int limit = -1, int offset = 0 ) = 0;
	virtual void loadCategories( CategoryTree *list, int limit = -1, int offset = 0, int parent_id = -1, bool recurse = true ) = 0;
	void loadCachedCategories( CategoryTree **list, int limit, int offset, int parent_id, bool recurse );
	virtual void loadCategories( ElementList *list, int limit = -1, int offset = 0 ) = 0;
	virtual void loadIngredientGroups( ElementList *list ) = 0;
	virtual void loadIngredients( ElementList *list, int limit = -1, int offset = 0 ) = 0;
	virtual void loadPossibleUnits( int ingredientID, UnitList *list ) = 0;
	virtual void loadPrepMethods( ElementList *list, int limit = -1, int offset = 0 ) = 0;
	virtual void loadProperties( IngredientPropertyList *list, int ingredientID = -2 ) = 0; // Loads the list of possible properties by default, all the ingredient properties with -1, and the ingredients of given property if id>=0
	void loadRecipe( Recipe *recipe, int items, int id );

	virtual void loadRatingCriterion( ElementList *list, int limit = -1, int offset = 0 ) = 0;
	/** Load all recipes with the ids in @param ids into the @ref RecipeList @param recipes */
	virtual void loadRecipes( RecipeList *, int items = All, TQValueList<int> ids = TQValueList<int>()/*, KProgressDialog *progress_dlg = 0*/ ) = 0;
	virtual void loadRecipeList( ElementList *list, int categoryID = -1, bool recursive = false ) = 0;
	virtual void loadUncategorizedRecipes( ElementList *list ) = 0;
	virtual void loadUnits( UnitList *list, Unit::Type = Unit::All, int limit = -1, int offset = 0 ) = 0;
	virtual void loadUnitRatios( UnitRatioList *ratioList, Unit::Type ) = 0;
	virtual void loadYieldTypes( ElementList *list, int limit = -1, int offset = 0 ) = 0;

	/** Change all instances of authors with id @param id2 to @param id1 */
	virtual void mergeAuthors( int id1, int id2 ) = 0;

	/** Change all instances of categories with id @param id2 to @param id1 */
	virtual void mergeCategories( int id1, int id2 ) = 0;

	virtual void mergeIngredientGroups( int id1, int id2 ) = 0;

	/** Change all instances of ingredients with id @param id2 to @param id1 */
	virtual void mergeIngredients( int id1, int id2 ) = 0;

	/** Change all instances of units with id @param id2 to @param id1 */
	virtual void mergeUnits( int id1, int id2 ) = 0;

	/** Change all instances of prep methods with id @param id2 to @param id1 */
	virtual void mergePrepMethods( int id1, int id2 ) = 0;

	virtual void mergeProperties( int id1, int id2 ) = 0;


	virtual void modIngredientGroup( int ingredientID, const TQString &newLabel ) = 0;
	/**
	* set newLabel for ingredientID
	*/
	virtual void modIngredient( int ingredientID, const TQString &newLabel ) = 0;
	/**
	* set newLabel for unitID
	*/
	virtual void modUnit( const Unit &unit ) = 0;
	/**
	* set newLabel for categoryID
	*/
	virtual void modCategory( int categoryID, const TQString &newLabel ) = 0;
	virtual void modCategory( int categoryID, int new_parent_id ) = 0;
	/**
	* set newLabel for authorID
	*/
	virtual void modAuthor( int authorID, const TQString &newLabel ) = 0;

	virtual void modPrepMethod( int prepMethodID, const TQString &newLabel ) = 0;

	virtual void modProperty( int propertyID, const TQString &newLabel ) = 0;

	virtual TQString recipeTitle( int recipeID ) = 0;

	virtual void removeAuthor( int categoryID ) = 0;
	virtual void removeCategory( int categoryID ) = 0;
	virtual void removeIngredientGroup( int ingredientID ) = 0;
	virtual void removeIngredient( int ingredientID ) = 0;
	virtual void removeIngredientWeight( int id ) = 0;
	virtual void removePrepMethod( int prepMethodID ) = 0;
	virtual void removeProperty( int propertyID ) = 0;
	virtual void removePropertyFromIngredient( int ingredientID, int propertyID, int perUnitID ) = 0;
	virtual void removeRecipe( int id ) = 0;
	virtual void removeRecipeFromCategory( int ingredientID, int categoryID ) = 0;
	virtual void removeUnit( int unitID ) = 0;
	virtual void removeUnitFromIngredient( int ingredientID, int unitID ) = 0;
	virtual void removeUnitRatio( int unitID1, int unitID2 ) = 0;

	virtual void saveRecipe( Recipe *recipe ) = 0;
	virtual void saveUnitRatio( const UnitRatio *ratio ) = 0;
	virtual void search( RecipeList *list, int items, const RecipeSearchParameters &parameters ) = 0;

	/** @returns true on success, false otherwise */
	ConversionStatus convertIngredientUnits( const Ingredient &from, const Unit &to, Ingredient &result );
	virtual double unitRatio( int unitID1, int unitID2 ) = 0;

	/** @returns the number of grams in the given amount of the ingredient, or -1 on failure */
	virtual double ingredientWeight( const Ingredient &ing, bool *wasApproximated = 0 ) = 0;
	virtual WeightList ingredientWeightUnits( int ingID ) = 0;

	virtual TQString escapeAndEncode( const TQString &s ) const = 0;
	virtual TQString unescapeAndDecode( const TQCString &s ) const = 0;

	virtual TQString categoryName( int ID ) = 0;
	virtual TQString ingredientName( int ID ) = 0;
	virtual TQString prepMethodName( int ID ) = 0;
	virtual IngredientProperty propertyName( int ID ) = 0;
	virtual Unit unitName( int ID ) = 0;

	virtual int categoryTopLevelCount() = 0;
	virtual int getCount( const TQString &table_name ) = 0;
	int authorCount();
	int ingredientCount();
	int prepMethodCount();
	int unitCount();
	int categoryCount();

	virtual bool checkIntegrity( void ) = 0;

	virtual void createTable( const TQString &tableName ) = 0;
	virtual void splitCommands( TQString& s, TQStringList& sl ) = 0;

	virtual float databaseVersion( void ) = 0;

	int maxAuthorNameLength() const
	{
		return 50;
	}
	int maxCategoryNameLength() const
	{
		return 40;
	}
	int maxIngredientNameLength() const
	{
		return 50;
	}
	int maxIngGroupNameLength() const
	{
		return 50;
	}
	int maxRecipeTitleLength() const
	{
		return 200;
	}
	int maxUnitNameLength() const
	{
		return 20;
	}
	int maxPrepMethodNameLength() const
	{
		return 20;
	}
	int maxPropertyNameLength() const
	{
		return 20;
	}
	int maxYieldTypeLength() const
	{
		return 20;
	}

	virtual bool ok()
	{
		return ( dbOK );
	}
	virtual TQString err()
	{
		return ( dbErr );
	}

	void updateCategoryCache( int limit );
	void clearCategoryCache();

protected:
	virtual void portOldDatabases( float version ) = 0;
	virtual TQStringList backupCommand() const = 0;
	virtual TQStringList restoreCommand() const = 0;

	//Use these with caution: SQL for one backend might not work on another!
	void execSQL( TQTextStream &stream );
	virtual void execSQL( const TQString & ) = 0;

	TQString buildSearchQuery( const RecipeSearchParameters &parameters ) const;

	double latestDBVersion() const;
	TQString krecipes_version() const;

	CategoryTree *m_categoryCache;

private:
	TQTextStream *dumpStream;
	bool haltOperation;

private slots:
	void processDumpOutput( TDEProcess *, char *buffer, int buflen );
};

#endif
