/***************************************************************************
                          mymoneyfinancialcalculator.h  -  description
                             -------------------
    begin                : Tue Oct 21 2003
    copyright            : (C) 2000-2003 by Michael Edwardes
    email                : mte@users.sourceforge.net
                           Javier Campos Morales <javi_c@users.sourceforge.net>
                           Felix Rodriguez <frodriguez@users.sourceforge.net>
                           John C <thetacoturtle@users.sourceforge.net>
                           Thomas Baumgart <ipwizard@users.sourceforge.net>
                           Kevin Tambascio <ktambascio@users.sourceforge.net>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 MYMONEYFINANCIALCALCULATOR_H
#define MYMONEYFINANCIALCALCULATOR_H

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cmath>
// ----------------------------------------------------------------------------
// QT Includes

// ----------------------------------------------------------------------------
// KDE Includes

// ----------------------------------------------------------------------------
// Project Includes

#include <kmymoney/export.h>

#ifdef _GLIBCPP_HAVE_MODFL
#define HAVE_LONG_DOUBLE  1
#endif

#ifndef HAVE_LONG_DOUBLE
#define HAVE_LONG_DOUBLE  0
#endif

#if HAVE_LONG_DOUBLE
#define FCALC_DOUBLE  long double
#else
#define FCALC_DOUBLE  double
#define modfl(a,b)    modf(a,b)
#define roundl(a)     round(a)
#define powl(a,b)     pow(a,b)
#define expl(a)       exp(a)
#define logl(a)       log(a)
#define floorl(a)     floor(a)
#define fabsl(a)      fabs(a)
#endif

/**
  * @author Thomas Baumgart
  */

/**
  * This class implements the financial calculator as found in GNUCash.
  * For a detailed description of the algorithms see
  * gnucash-1.8.5/src/doc/finutil.html.
  */
class KMYMONEY_EXPORT MyMoneyFinancialCalculator
{
public:
  MyMoneyFinancialCalculator();
  ~MyMoneyFinancialCalculator();

  /**
    * This method calculates the number of payments required to amortize
    * the loan. ir, pv, fv and pmt must be set. It sets the member variable
    * m_npp with the calculated value.
    *
    * @return number of periodic payments
    *
    * @exception If one of the required values is not set, a MyMoneyException
    *             will be thrown
    */
  const FCALC_DOUBLE numPayments();

  /**
    * This method calculates the amount of the payment (amortization and interest)
    * for the loan. ir, pv, fv and npp must be set. It sets the member variable
    * m_pmt with the calculated value.
    *
    * @return amount of payment
    *
    * @exception If one of the required values is not set, a MyMoneyException
    *             will be thrown
    */
  const FCALC_DOUBLE payment();

  /**
    * This method calculates the present value
    * for the loan. ir, pmt, fv and npp must be set. It sets the member variable
    * m_pv with the calculated value.
    *
    * @return present value of loan
    *
    * @exception If one of the required values is not set, a MyMoneyException
    *             will be thrown
    */
  const FCALC_DOUBLE presentValue();

  /**
    * This method calculates the future value
    * for the loan. ir, pmt, pv and npp must be set. It sets the member variable
    * m_fv with the calculated value.
    *
    * @return future value of loan
    *
    * @exception If one of the required values is not set, a MyMoneyException
    *             will be thrown
    */
  const FCALC_DOUBLE futureValue();

  /**
    * This method calculates the nominal interest rate
    * for the loan. fv, pmt, pv and npp must be set. It sets the member variable
    * m_ir with the calculated value.
    *
    * @return interest rate of the loan
    *
    * @exception If one of the required values is not set, a MyMoneyException
    *             will be thrown
    */
  const FCALC_DOUBLE interestRate();

  /**
    * This method calculates the interest due for the next payment according
    * to the equation
    *
    *   id[n] = (pv[n-1] + (X * pmt)) * i
    *
    *   with
    *
    *   - pv[n-1]\n
    *     the present value at the end of the last period
    *   - X\n
    *     0 for end of period payments, 1 for beginning of period payments
    *   - pmt\n
    *     the periodic payment amount and
    *   - i\n
    *     the effective interest rate
    *
    * pv[n-1] will be the value as set with setPv(), i will be calculated
    * from the nominal interest rate as set with setIr(), pmt will be the
    * value as set with setPmt() and X is determined by the argument to
    * setBep().
    *
    * @return the interest amount
    */
  const FCALC_DOUBLE interestDue(void) const;

  /**
    * This method sets the rounding precision to @p prec fractional
    * digits. The default of @p is 2. Rounding is applied to pv, pmt
    * and fv.
    *
    * @param prec Number of fractional digits after rounding.
    */
  void setPrec(const unsigned short prec = 2);

  /**
    * This method sets the number of payment periods to the value
    * passed in parameter @p npp. The length of a period is controlled
    * via setPF().
    *
    * @param npp number of payment periods
    */
  void setNpp(const FCALC_DOUBLE npp);

  const FCALC_DOUBLE npp(void) const { return m_npp; };

  /**
    * This method sets the payment frequency. The parameter @p PF
    * specifies the payments per year.
    *
    *  - 1 == annual
    *  - 2 == semi-annual
    *  - 3 == tri-annual
    *  - 4 == quaterly
    *  - 6 == bi-monthly
    *  - 12 == monthly
    *  - 24 == semi-monthly
    *  - 26 == bi-weekly
    *  - 52 == weekly
    *  - 360 == daily
    *  - 365 == daily
    *
    * @param PF length of payment period (default is 12 - monthly)
    */
  void setPF(const unsigned short PF = 12);

  /**
    * This method sets the compounding frequency. The parameter @p CF
    * specifies the compounding period per year.
    *
    *  - 1 == annual
    *  - 2 == semi-annual
    *  - 3 == tri-annual
    *  - 4 == quaterly
    *  - 6 == bi-monthly
    *  - 12 == monthly
    *  - 24 == semi-monthly
    *  - 26 == bi-weekly
    *  - 52 == weekly
    *  - 360 == daily
    *  - 365 == daily
    *
    * @param CF length of compounding period (default is 12 - monthly)
    */
  void setCF(const unsigned short CF = 12);

  /**
    * This method controls whether the interest will be calculated
    * at the end of the payment period of at it's beginning.
    *
    * @param bep if @p false (default) then the interest is due at the
    *            end of the payment period, if @p true at it's beginning.
    */
  void setBep(const bool bep = false);

  /**
    * This method controls whether the interest is compounded in periods
    * or continously.
    *
    * @param disc if @p true (default) then the interest is compounded in
    *             periods, if @p false continously.
    */
  void setDisc(const bool disc = true);

  /**
    * This method sets the nominal interest rate to the value passed
    * in the argument @p ir.
    *
    * @param ir nominal interest rate
    */
  void setIr(const FCALC_DOUBLE ir);

  const FCALC_DOUBLE ir(void) const { return m_ir; };

  /**
    * This method sets the present value to the value passed
    * in the argument @p pv.
    *
    * @param pv present value
    */
  void setPv(const FCALC_DOUBLE pv);

  const FCALC_DOUBLE pv(void) const { return m_pv; };

  /**
    * This method sets the payment amount to the value passed
    * in the argument @p pmt.
    *
    * @param pmt payment amount
    */
  void setPmt(const FCALC_DOUBLE pmt);

  const FCALC_DOUBLE pmt(void) const { return m_pmt; };

  /**
    * This method sets the future value to the value passed
    * in the argument @p fv.
    *
    * @param fv future value
    */
  void setFv(const FCALC_DOUBLE fv);

  const FCALC_DOUBLE fv(void) const { return m_fv; };

private:
  const FCALC_DOUBLE eff_int(void) const;
  const FCALC_DOUBLE nom_int(const FCALC_DOUBLE eint) const;
  const FCALC_DOUBLE rnd(const FCALC_DOUBLE x) const;

  const FCALC_DOUBLE _Ax(const FCALC_DOUBLE eint) const;
  const FCALC_DOUBLE _Bx(const FCALC_DOUBLE eint) const;
  const FCALC_DOUBLE _Cx(const FCALC_DOUBLE eint) const;
  const FCALC_DOUBLE _fi(const FCALC_DOUBLE eint) const;
  const FCALC_DOUBLE _fip(const FCALC_DOUBLE eint) const;

private:
  FCALC_DOUBLE          m_ir;   // nominal interest rate
  FCALC_DOUBLE          m_pv;   // present value
  FCALC_DOUBLE          m_pmt;  // periodic payment
  FCALC_DOUBLE          m_fv;   // future value
  FCALC_DOUBLE          m_npp;  // number of payment periods

  unsigned short        m_CF;   // compounding frequency
  unsigned short        m_PF;   // payment frequency

  unsigned short        m_prec; // precision for roundoff for pv, pmt and fv
                                // i is not rounded, n is integer

  bool                  m_bep;  // beginning/end of period payment flag
  bool                  m_disc; // discrete/continous compounding flag

  unsigned short        m_mask; // available value mask
  #define PV_SET        0x0001
  #define IR_SET        0x0002
  #define PMT_SET       0x0004
  #define NPP_SET       0x0008
  #define FV_SET        0x0010
};

#endif
