/************************* * * * * * * * * * * * * ***************************
    Copyright (c) 1999-2005 Ryan Bobko
                       ryan@ostrich-emulators.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.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.     
************************** * * * * * * * * * * * * **************************/

#ifndef _QHACC_H
#define _QHACC_H

#include "plugins.h"
#include "tableops.h"
#include "plugininfo.h"

#include <vector>
#include <memory>
#include <map>

#include <qfont.h>
#include <qcolor.h>
#include <qobject.h>
#include <qstring.h>
#include <qdatetime.h>

class MonCon;
class PluginManager;
class QHaccResultSet;

class QLibrary;


/**
 * @short QHacc personal financial software database engine
 * @author Ryan Bobko <ryan@ostrich-emulators.com>
 */
class QHacc : public QObject {
  Q_OBJECT
		public:
	friend class QHaccExt;
	static const int ROLLOVER, PIDATABASE, PIIMPORTER, PIEXPORTER;
	static const int PIREPORTER, PIGRAPHER, PITYPES;
	static const char * CURRENCYSEPARATOR;
	static const int COMPATV;   // datasets incompatible before this
	static const int COMPATVLF; // localfile datasets incompatible before this

	/**
	 * Creates an instance and finds all available plugins starting 
	 * in the given directory
	 *
	 * @param root Root of plugin-finding search
	 */
	QHacc( const char * root );
	
	/**
	 * Deletes the currently-loaded database plugin and closes 
	 * the library, if open
	 */
  ~QHacc();
	
	/**
	 * @return The current QHACC_HOME variable
	 */
	QString getHome() const;
	
	/**
	 * Sets the current QHACC_HOME variable and loads all the data therein
	 *
	 * @param QHACC_HOME A String representing the new datastore location
	 */
	bool setHome( const QString& QHACC_HOME );

	/**
	 * @return True, if the current QHACC_HOME is contained in 
	 * QHacc's native file structure
	 */
	bool homeIsLocalFiles() const;
	
	/**
	 * Save the current state of QHACC_HOME to the database
	 * @param error A description of any error thrown by the save
	 * @return True if the save was successful
	 */
	bool save( QString& error );

	/**
	 * Create an export of the database into an array of ResultSets
	 * @param rs The pre-sized array of ResultSets
	 */
	void exprt( QHaccResultSet * rs );

	/**
	 * Import the array of ResultSets into the database. This function allows
	 * direct database access, so it should be used with extreme care. 
	 * @param rs The pre-sized array of ResultSets
	 */
	void imprt( QHaccResultSet * rs );
	
	/**
	 * Create the given account
	 * @param a The account to be added
	 * @return the AID of the newly-created account
	 */
	uint addA( const Account& a );

	/**
	 * Update the Accounts table to make the account that looks like @p old look
	 * like @p newer
	 * @param old The account to be modified
	 * @param newer The new account data
	 */
	void updateA( const Account& old, const Account& newer );

	/**
	 * Remove the given account, all its transactions, and all its transactions'
	 * splits. It recalculates all account balances affected by the deletions.
	 * @param a The account to be removed
	 */
	void removeA( const Account& a );

	/**
	 * Returns the given account. The identifier can be the account's full name,
	 * its short name, or its id. Matches are checked in that order.
	 * @param str The account identifier.
	 * @return The account specified by the given string.
	 */
	Account getA( const QString& str );

	/**
	 * Returns the given account
	 * @param aid The account ID
	 * @return The account specified by the ID
	 */
	Account getA( uint aid );


	/**
	 * Get the full name of the given account. The full name is the account's
	 * name prefixed by its parent's account name. Top-level accounts have the
	 * same short name and full name.
	 * @param a The account in question
	 * @return The account's full name
	 */
	QString getFNameOfA( const Account& a );

	/**
	 * Get the full name of the given account. The full name is the account's
	 * name prefixed by its parent's account name. Top-level accounts have the
	 * same short name and full name.
	 * @param id The id of the account in question
	 * @return The account's full name
	 */
	QString getFNameOfA( uint id );

	/**
	 * Get all accounts
	 * @param tg Which columns to retrieve
	 * @param rs A dynamically-created ResultSet containing the desired accounts.
	 * (This must be deleted by the caller.)
	 */
	auto_ptr<QHaccResultSet> getAs( const TableGet& tg );

	/**
	 * Get a default account to be filled in later. This creates a row suitable
	 * for insertion into the Accounts table, but does not insert it. The row
	 * can be modified for later insertion.
	 *
	 * @return An account with default values set.
	 */
	Account getBlankA();

	/**
	 * Get the balance of the given account on the given day. This is the balance
	 * before any transactions have been entered for that day.
	 * @param a The account in question
	 * @param d The date
	 * @param ts A selection criteria for xtrans to count
	 * @return The balance of the account as of that date
	 */
	int getABalOn( const Account& a, const QDate& d, const TableSelect& ts );

	/**
	 * Determine if the given acccount increases on the left side of the
	 * T-balance or not
	 * @param a The account in question
	 * @return True, if the account is a Left-Plus Account
	 */
	bool isLPA( const Account& a );

	/**
	 * Add the given set of rows as one splitgroup. This assign a new TID to the
	 * added transaction and new SIDs to the splits.
	 * @param tr The transaction
	 * @param tbl The set of splits to link to @p tr
	 * @param forSched Should the transaction date be passed to resolveSums?
	 * @return the new TID of the just-added transaction, or 0 if failed
	 */
	uint addT( const Transaction& tr, QHaccTable& tbl, bool forSched =false );

	/**
	 * Delete the given transaction and all of its splits.
	 * Recalculate the affected account balances.
	 */
	void removeT( uint tid );

	/**
	 * Update the Transactions table to make the splitgroup specified by @p sg 
	 * contain the rows given in @p newsplits.
	 * affected account balances.
	 * @param tr The transaction to update
	 * @param newsplits The new splits to associate into the splitgroup
	 */
	void updateT( const Transaction& tr, QHaccTable& newsplits );

	/**
	 * Set the reconcile flag on the given split to the given state. Note
	 * that this function does not recalculate the balance of the split's
	 * parent account. The NR is for "No Recalculate."
	 * @param xt The Extended Transaction to change
	 * @param recmode the new reconcile mode
	 */
	void setRecNR( const Transaction& xt, uint recmode );

	/**
	 * Set the reconcile flag on the given transactions to the given state. This
	 * function will recalculate account balances as needed.
	 * @param t The set of extended transactions to change
	 * @param recmode the new reconcile mode
	 */	
	void setRec( QHaccTable * t, uint recmode );

	/**
	 * Get the extended transaction identified by the given id
	 * @param tid The transaction ID to return
	 * @return The specified transaction
	 */
	Transaction getT( uint tid );
	
	/**
	 * Create a table of Extended Transactions
	 * @param a The account to be queried
	 * @param tg Which columns to return
	 * @param ts An array of TableSelect objects
	 * @param tssz The size of the ts array
	 * @return True if the result set is not empty
	 */
	auto_ptr<QHaccResultSet> getXTForA( const Account& a, const TableGet& tg, 
																			vector<TableSelect>, uint& rows );

	/**
	 * Get splits associated with the given transaction. Unlike most of the
	 * getX functions, this one actually returns the QHaccTable instead of
	 * using a parameter. This is because we're assuming that the number of
	 * splits per transaction will generally be small enough to pass by value
	 * @param tid The transaction
	 * @return A QHaccTable of the transaction's splits
	 */
	QHaccTable getTSplits( uint tid );

	/**
	 * Get the memorized transactions for the given account.
	 * @param aid The account id in question
	 * @return True if the resultset is not empty
	 */
	auto_ptr<QHaccResultSet> getNTsForA( uint aid );

	/**
	 * Add the given transaction and splits to the transactions and splits
	 * tables, respectively. Also, add an entry to the namedtrans table
	 * @param nt The NamedTrans row to add to the namedtrans table
	 * @param t The transaction to add to the transactions table
	 * @param splits The splits to add to the splits table
	 * @return the NID of the newly-created namedtrans
	 */
	uint addNTForA( const TableRow& nt, const Transaction& t,
									QHaccTable& splits );

	/**
	 * Delete the given nametrans and any jobs that use it
	 */
	void removeNT( const QString& name );

	/** 
	 * Remove namedtrans and jobs as specified.
	 * @param id and ID. This will relate to the NTID field or the NACCTID field
	 * depending on @p idIsAID.
	 * @param idIsAID If True, then remove all namedtrans and jobs for @p id,
	 * which is an Account ID (use the NACCTID field for determine what gets
	 * deleted. If False, the the @p id argument is a TID, and the NTID field
	 * should be used to determine what gets axed
	 */
	void removeNTFor( uint id, bool idIsAID );

	/**
	 * Retrieve the specificed named transaction.
	 * @param name The name of the namedtrans to return
	 * @param t The transaction 
	 * @param splits The splits related to @p t
	 * @return The NamedTrans row, if it exists, or a null TableRow
	 */
	TableRow getNT( const QString& name, Transaction& t, QHaccTable& splits );

	/**
	 * Change the name of a named transaction
	 * @param nid The id of the named transaction to change
	 * @param nt The new namedtrans update
	 * @param t The transaction information
	 * @param s The split information
	 */
	void updateNT( uint nid, const TableRow& nt, const Transaction& t, 
								 QHaccTable& s );

	/**
	 * Determine if the given transaction and adjustment set make a 
	 * complete transaction. That is, can all the splits be resolved?
	 * This function assumes the splits have already gone through
	 * condenseSG.
	 * @param t The transaction
	 * @param s The splits
	 * @return True, if the splits can be resolved
	 */
	bool isResolvable( const Transaction& t, const QHaccTable& s );

	/**
	 * Create the given scheduled job
	 * @param j The job to be added
	 * @return the JID of the newly-created job
	 */
	uint addJ( const TableRow& j );

	/**
	 * Remove the given scheduled job
	 * @param jid The id of the job to be removed
	 */
	void removeJ( uint jid );

	/**
	 * Retrieve a scheduled job by name (QC::JWHAT)
	 * @param name The name of the job to be removed
	 * @return The given row, or a null row if no job exists
	 */
	TableRow getJ( const QString& name );

	/**
	 * Add the specified journal to the Journals table.
	 * @param l The journal to be added.
	 * @return the LID of the newly created journal
	 */
	uint addL( const Journal& l );

	/**
	 * Update the Journals table to make the journal that looks like @p old look
	 * like @p newer
	 * @param old The journal to be modified
	 * @param newer The new journal data.
	 */
	void updateL( const Journal& old, const Journal& newer );

	/**
	 * Remove the given journal, all its transactions, and all its transactions'
	 * splits. It recalculates all account balances affected by the deletions.
	 * @param l The journal to be removed
	 */
	void removeL( const Journal& l );

	/**
	 * Get all journals
	 */
	auto_ptr<QHaccResultSet> getLs();
	
	/**
	 * Get the given journal.
	 * @param str The journal's name
	 * @return The journal specified by the given string.
	 */
	Journal getL( const QString& str );
	
	/**
	 * Get the given journal
	 * @param lid The ID of the journal to be returned
	 * @return The journal specified
	 */
	Journal getL( uint lid );

	/**
	 * Get the specified preference
	 * @return the preference value as a string
	 */
	QString getSP( const QString& ) const;

	/**
	 * Get the specified preference
	 * @return the preference value as a color
	 */
	QColor getCP( const QString& ) const;

	/**
	 * Get the specified preference
	 * @return the preference value as a date
	 */
	QDate getDP( const QString& ) const;

	/**
	 * Get the specified preference
	 * @return the preference value as a font
	 */
	QFont getWP( const QString& ) const;

	/**
	 * Get the specified preference
	 * @return the preference value as a floating-point value
	 */
	float getFP( const QString& ) const;

	/**
	 * Get the specified preference
	 * @return the preference value as a boolean value
	 */
	bool getBP( const QString& ) const;

	/**
	 * Get the specified preference
	 * @return the preference value as an integer
	 */
	int getIP( const QString& ) const;

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setSP( const QString& pref, const QString& val );

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setCP( const QString& pref, const QColor& val );

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setDP( const QString& pref, const QDate& val );

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setWP( const QString& pref, const QFont& val );

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setFP( const QString& pref, float val );

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setBP( const QString& pref, bool val );

	/**
	 * Set the specified preference @p pref to the given value @p val.
	 * @param pref The preference in question
	 * @param val The new preference value
	 */
	void setIP( const QString& pref, int val );

	/**
	 * Combine a Transaction and a Split into an Extended Transaction.
	 * This function is for convenience only
	 */
	static TableRow makeXTrans( const Transaction& tr, const Split& spl );

	/**
	 * Extract Transaction and a Split info from an Extended Transaction.
	 * This function is for convenience only
	 */
	static void splitXTrans( const TableRow& xt, Transaction& tr, Split& spl );

	/**
	 * Extract just the Transaction information from an Extended Transaction.
	 * This function is for convenience only
	 */
	static Transaction splitXTrans( const TableRow& xt );

	/**
	 * Take a whole-number value of money and create a string of that value,
	 * including decimals.
	 * @param cents The integer value of money
	 * @return A string representing the integer as fractional money.
	 */
	//static QString convMoney( int cents );

	/**
	 * Take a string with decimal places and convert it to an integer
	 * representation.
	 * @param str The string containing decimals
	 * @return The integer representation of the money.
	 */
	//static int convMoney( const QString& str );

	/**
	 * Get the directory containing i18n data from the preconf file.
	 * @return The directory containing i18n data.
	 */
	QString languagedir() const;
	
	/**
	 * Get the descriptions of available plugins of a given type
	 * @param type The type of plugins to return
	 * @param current if !=0, the location pointed to by this
	 * parameter will contain index of the currently-used plugin
	 * in the case that the type parameter is PIDATABASE
	 * @return a vector of PluginInfo objects describing the plugins
	 */
	vector<PluginInfo> getPluginInfo( int type, int * current =0 ) const;

	/**
	 * Given a home, pass a db plugin capable of reading that datastore
	 * @param type The type of plugin desired
	 * @param home The QHACC_HOME that needs to be read
	 * @param pi The QHaccPlugin that can read the given home, or the
	 * default QHACC_HOME reader if no plugin seems appropriate.
	 * (This must be deleted by the caller using destroyPlugin() )
	 * @return The home value with the plugin identifier removed. If the
	 * default plugin is used, return the unmodified home value.
	 */
	QString getPluginFor( int type, const QString& home,
												QHaccPlugin *& pi ) const;
	
	/**
	 * Destroy the given plugin. This requires the correct library to 
	 * be reloaded and the right destructor to be called from that library
	 * @param type The type of plugin we're dealing with
	 * @param pi The plugin to destroy
	 * @return True if the plugin was destroyed
	 */
	bool destroyPlugin( int type, QHaccPlugin * pi );

	/**
	 * Recalculate the balance, reconciled balance, and budget values of the
	 * given account.
	 * @param a The account to recalculate
	 * @return A copy of the new information in the Accounts table
	 */
	Account calcBalOfA( const Account& );

	/**
	 * Query the database
	 * @param table The table to query
	 * @param ts An array of TableSelect objects
	 * @param tssz The size of the ts array
	 */
	auto_ptr<QHaccResultSet> getWhere( Table table, vector<TableSelect>,
																		 uint& rows );

	/**
	 * Query the database
	 * @param table The table to query
	 * @param tg columns to retrieve
	 * @param ts An array of TableSelect objects
	 * @param tssz The size of the ts array
	 */
	auto_ptr<QHaccResultSet> getWhere( Table table, const TableGet& tg, 
																		 vector<TableSelect>, uint& rows );

	/**
	 * @return The number of rows in the given table
	 */
	uint cnt( Table );

	/**
	 * @param t The table on which to operate
	 * @param col The column in question
	 * @returns the minimum value of the column
	 */
	TableCol min( Table t, int col );

	/**
	 * @param t The table on which to operate
	 * @param col The column in question
	 * @returns the maximum value of the column
	 */
	TableCol max( Table t, int col );

	/**
	 * @returns a MonCon object for converting money
	 */
	const MonCon& converter() const;


 protected:
	/**
	 * Delete from the given table every row where the given column matches the
	 * given selection criteria. This function allows direct database access, so
	 * it should be used with extreme care. 
	 * @param t The table on which to operate
	 * @param ts The selection criteria
	 */
	void deleteWhere( Table t, const TableSelect& ts );

	/**
	 * Update the given table in the given way. This function allows direct 
	 * database access, so it should be used with extreme care. 
	 * @param t The table on which to operate
	 * @param ts The selection criteria
	 * @param tu What to update in the returned rows
	 */
	void updateWhere( Table t, const TableSelect& ts, const TableUpdate& tu );

	/**
	 * Load the given rows into the given table. This function allows direct
	 * database access, so it should be used with extreme care.
	 * @param t The table on which to operate
	 * @param rs The rows to be added
	 * @return True, if the rows are added
	 */
	bool load( Table t, const QHaccResultSet * rs );

	/**
	 * Add one row to the given table. This function allows direct
	 * database access, so it should be used with extreme care.
	 * @param t The table on which to operate
	 * @param tr The row to be added
	 * @return The error code of the addition
	 */
	uint add( Table t, const TableRow& tr );

	/**
	 * Run the scheduled-job runner
	 */
	void processor();

	/**
	 * Remove extraneous transactions from the given table. This condenses
	 * transactions with duplicate accounts into one transaction.
	 * @param t A set of splits
	 * @return True, if the newly-condensed @p t is valid for insertion.
	 */
	bool condenseSG( QHaccTable * t ) const;

	/**
	 * Validate that the sums of the splits are balances. This function will
	 * convert percentages and remainders to actual sums
	 * @param t A set of splits
	 * @param alterTable Set to True if percentage sums in @p t should be 
	 * altered to their actual values
	 * @param d The date on which any account percentages should be resolved to
	 * @return True, if the newly-resolved @p t is valid for insertion.
	 */
	bool resolveSums( QHaccTable * t, bool alterTable, QDate d );

	/**
	 * Recalculate all account balances, and if different than expected, reset
	 * the account's opening balance to keep the current balance constant.
	 */
	void resetOBals();

 private:
	QString qhacchome, langdir;
	QHaccDBPlugin * db;
	PluginManager ** plugmen;
	bool processJobs;
	map<QString, QString> prefcache; // a cache of pref items, so no db lookup
	MonCon * conv; 


	/**
	 * All preferences are kept as strings in the database. This function
	 * is called by all the other setP functions to actually maintain the
	 * database state.
	 */
	bool isetP( const QString& pref, const QString& val );

	/**
	 * Get a preference from the database.
	 * @return null string if preference doesn't exist, or the value if it does
	 */
	QString igetP( const QString& pref ) const;

	/**
	 * Search the given directory for plugins and load them. Also, set any
	 * preconfiguration options specified in the preconf file.
	 * @param rootdir The top of the searchable directory structure.
	 */
	void readpre( const QString& rootdir );

 signals:
	/**
	 * Added the given account
	 */
	void addedA( const Account& );

	/**
	 * Removed the given account
	 */
	void removedA( const Account& );

	/**
	 * Updated the account @p old to look like @p newer
	 * @param old The old account
	 * @param newer The new account	 
	 */
	void updatedA( const Account& old, const Account& newer );

	/**
	 * The given account is out of balance.
	 * @param a The account in question
	 * @param bal The new balance of the account
	 */
	void overBudget( const Account& a, int bal );

	/**
	 * Added the given transaction
	 */
	void addedT( const Transaction& );

	/**
	 * Removed the given transaction
	 */
	void removedT( const Transaction& );

	/**
	 * updated the given transaction
	 */
	void updatedT( const Transaction& );

	/**
	 * Added any transaction
	 */
	void addedT();

	/**
	 * Removed any transaction
	 */
	void removedT();

	/**
	 * Updated any transaction
	 */
	void updatedT();

	/**
	 * Added the given journal
	 */
	void addedL( const Journal& );

	/**
	 * Removed the given journal
	 */
	void removedL( const Journal& );

	/**
	 * Updated the journal @p old to look like @p newer
	 * @param old The old journal
	 * @param newer The new journal
	 */
	void updatedL( const Journal& old, const Journal& newer );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, QString );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, QColor );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, QDate );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, QFont );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, float );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, bool );

	/**
	 * Changed the given preference
	 */
	void changedP( const QString&, int );

	/**
	 * The database is in a dirty state
	 * @param b True, if the database should be saved
	 */
	void needSave( bool b );
};


class PluginManager {
 public:
	PluginManager( const QString& root, const QString& extension );
	virtual ~PluginManager();
	
	virtual QString getPluginFor( const QString& key, QHaccPlugin *& pi ) const;
	virtual bool destroyPlugin( QHaccPlugin * pi );
	virtual vector<PluginInfo> getPluginInfo() const;

 private:
	int * pluginUsage;
	vector<PluginInfo> info;
	QLibrary ** libs;
};

#endif
