;; -*-scheme-*-

;; ------------------------------------------------------------------
;; GNUCASH ASSET PERFORMANCE REPORT
;; ------------------------------------------------------------------

;; This report collects a result list of following format per element:
;; (
;;   year of evaluation
;;   balance at start of year of evaluation
;;   balance at start of evaluation period (given as option)
;;   (
;;     debit into the selected accounts within the year of evaluation
;;     change of balance within year of evaluation
;;     debit into the selected accounts since start of evaluation period until year of evaluation
;;     change of balance since start of evaluation period
;;   )
;; )
;; There will be one element for each fiscal year.

;; you can see this list as debug output by running gnucash in debug mode:
;; > gnucash --debug --log gnc.scm=debug &
;; and then grep for BNCHMKRP1 in the gnucash log file
;; > grep BNCHMKRP1 /tmp/gnucash.trace

;; The yearly performance is calculated according to following formular:
;; 
;;          C_e - C_s - 12 D_m
;; I_r =  ---------------------------- 
;;         13/2 * D_m + C_s
;;
;; with
;;
;;       I_r = the interest for the current year (i.e. the actual performance value)
;;       C_s = the capital at start of the year (i.e. the starting balance)
;;       C_e = the capital at end of the year (i.e. the ending balance)
;;       D_m = the monthly deposit
;;
;; whereas
;;
;;       D_m = debit into the selected accounts within the year of evaluation (see above) / 12
;;
;; This formular is implemented in (define get-benchmark ...)

;; The accumulated performance is calculated based on following formular:
;;
;; 
;;                                    i
;;                                   ---
;;                       i           \             j
;;  C_i =  C_s (I_r + 1)     +  D_y       (I_r + 1)       ;; i = 1,2,3,...
;;                                   /    
;;                                   ---
;;                                   j=1
;; with
;;
;;       i   = the year counter from start of evaluation until the current year of evaluation
;;       I_r = the interest for the years 1...i (i.e. the actual performance value reached until the year of evaluation)
;;       C_s = the capital at start of the evaluation period (i.e. the starting balance at the beginning of year 1)
;;       D_y = the yearly deposit
;;
;; whereas
;;
;;       D_y = debit into the selected accounts until the year of evaluation (see above) / number of years (i)
;;
;; As this formular cannot be resolved analytically, it is approximated by iteratively trying out
;; different interest rest rates from very bad too very good until reaching an interest rate that would
;; give a better result than reached by the the selected accounts.
;;
;; This iteration is implemented in 
;;    
;;    (define interest-on-interest .......... )
;;    (define get-trial-kap-result .......... )
;;    (define get-superior-interest-rate .... )
;;
;; The range of trial interest rates is determined by the options for the "expected result corridor".
;; This is used to avoid unnecessary processing duration (which can be in the range of minutes, depending
;; on the amount of user data) and infinite looping.

;; Examples are given in the purpose description which is part of the report (see below).

;; ------------------------------------------------------------------
;; Top-level definitions
;; ------------------------------------------------------------------

(define-module (gnucash report performance-report))
(use-modules (gnucash main)) ;; needed for gnc:debug
(use-modules (gnucash printf))
(use-modules (gnucash gnc-module))
(gnc:module-load "gnucash/report/report-system" 0)
(gnc:module-load "gnucash/html" 0) ;for gnc-build-url


(define reportname (N_ "Performance Report"))
(define version "0.10")

(define DBG_1 "BNCHMKRP1") ;; debug level 1
(define DBG_2 "BNCHMKRP2") ;; debug level 2 for getInFlowOfType
(define DBG_3 "BNCHMKRP3") ;; debug level 3 for get-superior-interest-rate

(define day-duration  86400) ;; day duration in seconds
(define year-duration (* 365 day-duration)) ;; year duration in seconds

(define purposetext-headline (N_ "Purpose"))
(define purposetext-paragraph01 (N_
"This Report is intended to measure the performance of asset accounts. <br>\
The main idea is to visualize the performance of financial programs like life <br>\
insurances or fonds based saving plans in comparison with simple saving accounts. <br>\
Nevertheless, this report can be run against any account."
))

(define purposetext-paragraph02 (N_
"The performance is measured as interest rate that should have been applied to an <br>\
imaginary savings account leading to the same result as shown by the selected real account. <br>\
There are two measurements included in this report:<br> \
One on a per year basis, omitting the history of previous years,<br> \
and the other one including the history from start of the performance measurement <br>\
time period until the year of evaluation, called accumulated result (accumulated <br>\
over several years)."
))

(define purposetext-paragraph03 (N_
"The per-year-result (yearly result) evaluates each year of the measurement time <br>\
period in an isolated view. Each year has a balance at the beginnging of the year, <br>\
a balance at the end of the year, and deposits into the selected accounts throughout the year.<br>\
The exact time of the deposits will be discarded, instead, it is assumed that the <br>\
deposits have been done on a monthly basis with the average value (total deposit / 12 ).<br>\
The result of this measurement is the calculated interest rate of a simple savings account <br>\
with the same performance."
))

(define purposetext-paragraph04 (N_
"Example:<br>\
Only one account is selected, the measurement period has been adjusted to span <br>\
over only one year. The starting balance at the beginning is 100, in April 200 <br>\
have been dosited, in October 140 have been withdrawn, and one year after the <br>\
beginning of the measurement period the balance is 110.<br>The algorithm tranlates <br>\
the deposits and withdrawals into a total deposit of 60 (=200-140) split into 12 <br>\
monthly deposits of 5. In this example the total of the balance at year start (100) <br>\
plus the total deposits (60) are greater then the balance at year end (110), meaning <br>\
that a corresponding saving account has a negative performance with a negative <br>\
interest rate, i.e. instead of receiving interest fee payments have been made."
))

(define purposetext-paragraph05 (N_
"The accumulated result takes all years before the year of evaluation and the <br>\
year of evaluation into account. Deposits within of all the years up to the year <br>\
of evaluation are split into equally large deposits per year. The resulting <br>\
interest rate applies for all years from the start of the measurement period <br>\
until the current year of evaluation."
))

(define purposetext-paragraph06 (N_
"Example:<br>\
Only one account is selected, the measurement period has been adjusted to span <br>\
over two years. The starting balance at the beginning is 100, in April of the <br>\
first year 200 have been dosited, in October of the second year 140 have been <br>\
withdrawn, one year after the beginning of the measurement period (end of first year) <br>\
the balance is 110, at the end of the second year the balance is 180.<br>For the first <br>\
year, the algorithm assumes that a deposit of 200 has been done at the beginning of <br>\
the year, so the interest will be calculated on a starting balance of 300 (=100+200)<br> \
With a year-end balance of 110 the performance value will be negative, in fact <br>\
almost the whole deposit of this year has been lost giving a corresding negative <br>\
result for the first year. For the second year, the algorithm sees a total deposit <br>\
of 60 (=200-140) and assumes that 30 of those have been available at the start of <br>\
the first year, and the other 30 at the beginning of the second year.<br>The resulting \
interest that is valid for both years is based on three parts:<br>Interest paid on the <br>\
starting balance of 130 (100+30) for the first year, interest paid for the result <br>\
reached until the beginning of the second year (130 + (interest on 130) ), and finally <br>\
interest on the second year's deposit of 30. The interest rate is the same for all <br>\
three parts and the algorithm finds the suitable interest rate that leads to the final <br>\
balance of 180."
))

(define purposetext-paragraph07 (N_
"NOTE: Deposits must come from a BANK account (meaning, accounts that have been <br>\
set to type BANK in GnuCash). This is to cover the case that you get e.g. payments <br>\
from the state into the selected account as for public subsidized programs like <br>\
old age pension saving plans, and which should be booked in corrsponding income <br>\
accounts. Only payments from the bank accounts are seen as personal contribution, <br>\
and payments from outside the bank accounts are treated as additional interest payments."
))

;; ------------------------------------------------------------------
;; Define the Options for this report
;; ------------------------------------------------------------------

(define (options-generator)    
  (let* (
	  (options (gnc:new-options))
	  (add-option 
            (lambda (new-option)
              (gnc:register-option options new-option)
	    )
	  )
	)

        ;; ---------------------------------------------------------------------------
        ;; ACCOUNTS page
        ;; ---------------------------------------------------------------------------

        (add-option
          (gnc:make-account-list-option
             gnc:pagename-accounts                  ;; page name 
             (N_ "Accounts")                        ;; option name
             "a"                                    ;; position within the page
             (N_ "This is an account list option")  ;; help text
             ;; FIXME : this used to be gnc:get-current-accounts, but 
             ;; that doesn't exist any more.
             (lambda () '())
             #f 
             #t
          )
        )

        ;; ---------------------------------------------------------------------------
        ;; GENERAL page
        ;; ---------------------------------------------------------------------------

	(add-option
          (gnc:make-date-option
            gnc:pagename-general           ;; page name
            (N_ "Start of Benchmark Period")
            "b" 
            (N_ "Set the start of the benchmark period")
            (lambda () (cons 'relative 'start-prev-year))
            #f 
           'both 
           '(start-cal-year start-prev-year)
          ) 
        )
    
        (add-option
          (gnc:make-date-option
            gnc:pagename-general           ;; page name
            (N_ "End of Benchmark Period")
            "c" 
            (N_ "Set the end of the benchmark period")
            (lambda () (cons 'relative 'end-prev-year))
            #f 
           'both 
           '(end-cal-year end-prev-year)
          ) 
        )

        (add-option
          (gnc:make-multichoice-option
            gnc:pagename-general           ;; page name
            (N_ "Expected Result Corridor - Upper Limit")
            "d" 
            (N_ "The smaller the corridor the short the report processing time.")
            '20
            (list 
              (list->vector
                (list 
                  '40 ;;first
                  (N_ "40%")
                  (N_ "Sell!")
                )
              )
              (list->vector
                (list 
                  '30 ;;second
                  (N_ "30%")
                  (N_ "Exceptional performance")
                )
              )
              (list->vector
                (list 
                  '20 ;;third
                  (N_ "20%")
                  (N_ "Excellent performance")
                )
              )
              (list->vector
                (list 
                  '10 ;;fourth
                  (N_ "10%")
                  (N_ "Very good performance")
                )
              )
            )
          )
        )

        (add-option
          (gnc:make-multichoice-option
            gnc:pagename-general           ;; page name
            (N_ "Expected Result Corridor - Lower Limit")
            "e" 
            (N_ "The smaller the corridor the short the report processing time.")
            '-20
            (list 
              (list->vector
                (list 
                  '-10 ;;first
                  (N_ "-10%")
                  (N_ "Sad performance")
                )
              )
              (list->vector
                (list 
                  '-20 ;;second
                  (N_ "-20%")
                  (N_ "Don't panic")
                )
              )
              (list->vector
                (list 
                  '-30 ;;third
                  (N_ "-30%")
                  (N_ "Unbelievable low performance")
                )
              )
              (list->vector
                (list 
                  '-40 ;;fourth
                  (N_ "-40%")
                  (N_ "Panic!")
                )
              )
              (list->vector
                (list 
                  '-80 ;;fourth
                  (N_ "-80%")
                  (N_ "???")
                )
              )
            )
          )
        )

        ;; ---------------------------------------------------------------------------
        ;; DISPLAY page
        ;; ---------------------------------------------------------------------------

        (add-option
          (gnc:make-simple-boolean-option
            gnc:pagename-display           ;; page name
            (N_ "Show Result Table") 
            "f" 
            (N_ "Display the yearly results in tabular format")
            #t
          )
        )

        (add-option
          (gnc:make-simple-boolean-option
            gnc:pagename-display           ;; page name
            (N_ "Show Result Graph") 
            "g" 
            (N_ "Display the yearly results in a graph over time")
            #t
          )
        )

        (add-option
          (gnc:make-simple-boolean-option
            gnc:pagename-display           ;; page name
            (N_ "Show Purpose") 
            "h" 
            (N_ "Display a purpose description for this report.")
            #t
          )
        )
      
        ;; ---------------------------------------------------------------------------
        ;; return value
        ;; ---------------------------------------------------------------------------

    	options ;; RETURN value
  )
)

;; ------------------------------------------------------------------
;; Calls to the GnuCash library
;; ------------------------------------------------------------------

(define get-account-name
  (lambda (acct)
       (xaccAccountGetName acct)
  )
)

(define get-account-description
  (lambda (acct)
       (xaccAccountGetDescription acct)
  )
)

;; copied from report-utilities.scm without any change
;; needed by (account-get-balance-intervall ...) below

(define (gnc-commodity-collector-assoc-pair collector commodity sign?)
  (collector 'getpair commodity sign?))

;; copied from report-utilities.scm: (gnc:account-get-balance-at-date account date include-children?) 
;; and modified to use -intervall instead of -at-date
;; ===
;; This should be added to report-utilities.scm anyway !!!!!
;; ===

(define (account-get-balance-intervall account s-date e-date include-children?)
  (let* (
          (collector 
            (gnc:account-get-comm-value-interval
               account 
               s-date  
               e-date  
               include-children? 
             )
           )
        ;;)
           (balance 
	     (cadr 
               (gnc-commodity-collector-assoc-pair
	         collector 
                 (xaccAccountGetCommodity account) 
                 #f
               )
             )
           )
         )

    (/ (gnc:gnc-numeric-num balance) (gnc:gnc-numeric-denom balance)) ;; RETURN value 

  )
)

;; --------------------------------------------------------------------------------------
;; check if an account is a list element (stolen from cash-flow.scm)
;; --------------------------------------------------------------------------------------

(define (same-account? a1 a2)
  (string=? (gncAccountGetGUID a1) (gncAccountGetGUID a2))
)

(define account-in-list?
  (lambda (account accounts)
    (cond
      ((null? accounts) #f)
      ((same-account? (car accounts) account) #t)
      (else 
        (account-in-list? account (cdr accounts))
      )
    )
  )
)


;; --------------------------------------------------------------------------------------
;; get the account with a given account name
;; --------------------------------------------------------------------------------------

(define getAccountByFullName
  (lambda (accountName)

    (let* (
            ;; -----------------------------------------------
            ;; get the list of all accounts
            ;; -----------------------------------------------

            (accountList (gnc-account-get-descendants-sorted (gnc-get-current-root-account)))
	    (matchingAccount (car accountList))
          )

          (for-each 
            (lambda (account)
	      (if (string=? accountName (gnc-account-get-full-name account))
	        (set! matchingAccount account)
	      )
            )
            accountList
          )
          matchingAccount ;; RETURN value
    )
  )
)


;; --------------------------------------------------------------------------------------
;; collect the money that flows into a given account
;; --------------------------------------------------------------------------------------

(define getInFlowOfType
  (lambda (account accountlist start-date end-date sourceAccType)

    (gnc:debug DBG_2 
               " getInFlowOfType was called with: " 
               " account: " account 
               " accountlist: " accountlist 
               " start-date " start-date 
               " end-date " end-date 
               " sourceAccType: " sourceAccType
    )

    (let* (
            ;; -----------------------------------------------
            ;; get the list of transactions for this account
            ;; -----------------------------------------------

           (splitList (xaccAccountGetSplitList account))

            ;; -----------------------------------------------
            ;; initialize the return value
            ;; -----------------------------------------------

           (flowValue 0.0)
          )

          ;; -----------------------------------------------
          ;; analyse each transaction
          ;; -----------------------------------------------

          (for-each 
            (lambda (split)

	      (let* (
		      (splitValue      (xaccSplitGetValue split))
		      (splitDate       (gnc-transaction-get-date-posted (xaccSplitGetParent split)))
	              (accountFullName (gnc-account-get-full-name  account))
		      (corrAccFullName (xaccSplitGetCorrAccountFullName split))
		      (corrAccount     (getAccountByFullName corrAccFullName))
		      (corrAccType     (xaccAccountGetType corrAccount))
		    )

                    ;; ---------------------------------------------
                    ;; only consider transactions within the given time frame
                    ;; ---------------------------------------------

		    (if (and 
                              ;;(<= (car start-date) (car splitDate))
                              ;;(>= (car end-date)   (car splitDate)) 
                              (<= start-date (car splitDate))
                              (>= end-date   (car splitDate)) 
			)

                        ;; ---------------------------------------------
                        ;; only consider transactions from or to accounts outside of the list of selected accounts
                        ;; ---------------------------------------------

                        (if (not (account-in-list? corrAccount accountlist))

                          ;; ---------------------------------------------
                          ;; only consider transactions from accounts of given type
                          ;; ---------------------------------------------

                          (if (eq? corrAccType sourceAccType) 

                            ;; ---------------------------------------------
                            ;; process up the transaction value
                            ;; note that both positive and negative flows need to be considered
                            ;; meaning: money flowing back to the source accounts will decrease the value of the total InFlow
                            ;; ---------------------------------------------

			    (let (
				   (splitValueNumeric (/ (gnc:gnc-numeric-num splitValue) (gnc:gnc-numeric-denom splitValue)) )
                                 )                                 

			      (set! flowValue (+ splitValueNumeric flowValue))

                              (gnc:debug DBG_2 
                                         " getInFlowOfType Result: " 
                                         " Date " (gnc-print-date splitDate) 
                                         " Split " splitValue
                                         " Flow " flowValue 
                                         " Source " corrAccFullName 
                                         " Destination " accountFullName
                              )
			    )
                          )
                        )
		    )
	      )  
            )
            splitList
          )
      flowValue ;; RETURN value
    )
  )
)

;; --------------------------------------------------------------------------------------
;; 
;; --------------------------------------------------------------------------------------
(define getListOfYears
  (lambda (start-date end-date)

    (define listOfYears
      (lambda (fy ly ny)
        (if (eq? (- ly fy) ny)
	  (list (+ fy ny))	
	  (cons (+ fy ny) (listOfYears fy ly (+ ny 1)))
        )
      )
    )

    (let*
      (
	(first-year (string->number (substring (gnc-print-date start-date) 0 4)))
	(last-year  (string->number (substring (gnc-print-date end-date) 0 4)))
	(number-of-years 0)
      )
      (listOfYears first-year last-year number-of-years)
    )
  )
)

;; --------------------------------------------------------------------------------------
;; 
;; --------------------------------------------------------------------------------------
(define merge-result-lists
  (lambda (list1 list2)

    (define merge-cdrs
      (lambda (cdr1 cdr2)
        (map (lambda (x y) (+ x y)) cdr1 cdr2)
      )
    )

    (let
        (
          (merged-list (list))
        )

        (for-each
          (lambda (x y) 
            (set! merged-list (cons
                                (list
                                  (car x) ;; year
                                  (+ (car (cdr x)) (car (cdr y))) ;; balance-at-year-start
                                  (+ (car (cdr (cdr x))) (car (cdr (cdr y)))) ;; balance-at-overall-start
                                  (merge-cdrs (cadr (cdr (cdr x))) (cadr (cdr (cdr y)))) ;; result-data
                                ) 
	                        merged-list
                              )
            )
          ) 
          list1 list2
        )
        merged-list ;; RETURN value 
    )
  )
)
;; --------------------------------------------------------------------------------------
;; 
;; --------------------------------------------------------------------------------------
(define get-benchmark-list
  (lambda (result-list result-corridor)

    (define get-benchmark
      (lambda (kap-delta kap-start contribution)
        (let 
          (
            (monthly-contribution (/ contribution 12))
          )
          (if
            (and
              (zero? kap-start)
              (zero? monthly-contribution)
            )
            ;; then avoid division by zero
            0.0
            ;; else
	    (/
              (round (* 10000 (/ (- kap-delta contribution) (+ (* 6.5 monthly-contribution) kap-start) )))
              100
            )
          )
        )
      )
    )

    (define interest-on-interest
      (lambda (year-counter max-year interest)
        (cond 
	  (
            (eq? year-counter max-year)
            (expt (+ 1 (/ interest 100)) year-counter)
          ) 
	  (
            else
            (+ 
              (expt (+ 1 (/ interest 100)) year-counter)
              (interest-on-interest (+ year-counter 1) max-year interest)
            )
          ) 
        )
      )
    )

    (define get-trial-kap-result
      (lambda (kap-start contribution-per-year interest-rate num-years)
        (let* (
	        (contribution-interest-factor (interest-on-interest 1 num-years interest-rate))
	        (kap-start-interest-factor (expt (+ 1 (/ interest-rate 100)) num-years))
              )
            (+
               (* contribution-per-year contribution-interest-factor)
               (* kap-start                kap-start-interest-factor)
            )
        )
      )
    )

    (define get-superior-interest-rate
      (lambda (kap-start kap-end contribution num-years interest-rate)
        (let* (
	        (trial-kap-result (get-trial-kap-result kap-start (/ contribution num-years) interest-rate num-years))
              )
          (gnc:debug DBG_3 
                     " get-superior was called with"
                     " interest: " interest-rate 
                     " Kap-Start: " kap-start 
                     " Kap-End: " kap-end 
                     " Contribution: " contribution 
                     " Number of Years: " num-years
          ;;)
          ;;(gnc:debug DBG_3 
                     " Trial Kap Result: " trial-kap-result
          )
          (if 
            (and 
              (zero? kap-start)
              (zero? contribution)
            )
            ;; then
            0 
            ;; else
            (if (< trial-kap-result kap-end)
                (if (>= interest-rate (car result-corridor))
                  0 ;; RETURN value
                  ;; else
                  (get-superior-interest-rate 
                     kap-start 
                     kap-end 
                     contribution
                     num-years 
                     ;;(+ interest-rate 0.01) -> this leads to strange rounding errors, better use:
                     (/ (round (* 100 (+ interest-rate 0.01))) 100)
                  )
                )
                ;; else
                interest-rate ;;  RETURN value
            )
          )
        )
      )
    )

    (let
        (
          (benchmark-list (list))
          (year-counter 0)
        )

        (for-each
          (lambda (year-result) 
            (set! benchmark-list 
              (cons 
                (let* (
                       (year (car year-result)) ;; year 
                       (balance-at-year-start (car (cdr year-result)))
                       (balance-at-overall-start (car (cdr (cdr year-result))))
                       (result-data (cadr (cdr (cdr year-result))))
                       (contribution-year (car result-data))
                       (balance-delta-year (car (cdr result-data)))
                       (contribution-overall (car (cdr (cdr result-data))))
                       (balance-delta-overall (cadr (cdr (cdr result-data))))
                       (num-years (- (length result-list) year-counter))
                      )
                  (list
                    year
                    (get-benchmark 
                      balance-delta-year
                      balance-at-year-start
                      contribution-year
                    )
                    ;;(get-benchmark balance-at-overall-start balance-delta-overall contribution-overall)
                    (get-superior-interest-rate
                      balance-at-overall-start
                      (+ balance-at-overall-start balance-delta-overall)
                      contribution-overall
                      num-years
                      (cadr result-corridor)
                    )
                  ) ;; benchmark data
                )
                benchmark-list
              )
            )
            (set! year-counter (+ year-counter 1))
          ) 
          result-list
        )
        benchmark-list ;; RETURN value 
    )
  )
)

;; --------------------------------------------------------------------------------------
;; 
;; --------------------------------------------------------------------------------------
(define get-account-performance
  (lambda (accountlist start-date end-date result-corridor)

    (define get-gnc-balance
      (lambda (acct date)
        (let* (
                (bal 
                  (gnc:account-get-balance-at-date
                    acct 
                    date
                    #f ;; include children?
                  )
                )
                (num (gnc:gnc-numeric-num bal))
                (denom (gnc:gnc-numeric-denom bal))
              )
              (/ num denom 1.0)
        )
      )
    )

    (define sum-of-long-years-before
      (lambda (year)
        (let ( (prev-year (- year 1)))
          (if (< prev-year 1970)
            0
            (if (zero? (modulo prev-year 4))
              (+ 1 (sum-of-long-years-before prev-year))
              (+ 0 (sum-of-long-years-before prev-year))
            )
          )
        )
      ) 
    )

    (define get-start-day-in-the-year
      (lambda (year)
        (let* (
		 (start-second-of-current-year (+ (* (sum-of-long-years-before year) day-duration) (* (- year 1970) year-duration)))
                 (start-second-of-fiscal-year (+ (car start-date) 3600))
                 (days-passed-in-current-year (round (/ (- start-second-of-fiscal-year start-second-of-current-year) day-duration)))
             )
          (if (< days-passed-in-current-year 59)
            (list days-passed-in-current-year days-passed-in-current-year)
            (if (zero? (modulo year 4))
              (list (- days-passed-in-current-year 1) days-passed-in-current-year)
              (list days-passed-in-current-year (+ 1 days-passed-in-current-year))
            )
          )
        )
      ) 
    )

    (let* 
      (
        (start-day-new 0)
        (end-day-new 0)
	(list-of-years (getListOfYears start-date end-date)) ;; years of the fiscal period
	(passed-days-in-year-before-start-day (get-start-day-in-the-year (car list-of-years)))
        (result-list-account (list))
        (result-list-total (list))
      )
     
      ;; ------------------------------------------------------
      ;; process all selected accounts
      ;; ------------------------------------------------------
      (for-each 
        (lambda (account)
    
          ;; ------------------------------------------------------
          ;; process all identified years
          ;; ------------------------------------------------------
  	  (for-each
  	    (lambda (year)

              ;; ------------------------------------------------------
              ;; calculate the start date of this fiscal year
              ;; ------------------------------------------------------
              (set! start-day-new
                (if (zero? (modulo year 4)) ;; is this a long year? does it have a 29th Feb?
                   (if (> (car passed-days-in-year-before-start-day) 58) ;; does the fiscal year start after the 28th Feb?
                     (+ (- (* (cadr passed-days-in-year-before-start-day) day-duration) 3600) (* (sum-of-long-years-before year) day-duration) (* (- year 1970) year-duration))
                     (+ (- (* (car passed-days-in-year-before-start-day) day-duration) 3600) (* (sum-of-long-years-before year) day-duration) (* (- year 1970) year-duration))
                   )
                   (+ (- (* (car passed-days-in-year-before-start-day) day-duration) 3600) (* (sum-of-long-years-before year) day-duration) (* (- year 1970) year-duration))
                )
              )

              ;; ------------------------------------------------------
              ;; calculate the end date of this fiscal year
              ;; ------------------------------------------------------
              (set! end-day-new
                (if (zero? (modulo year 4)) ;; is this a long year? does it have a 29th Feb?
                  (if (> (car passed-days-in-year-before-start-day) 58) ;; does the fiscal year start after the 28th Feb?
                    (+ start-day-new -2 year-duration)  ;; end-date
                    (+ start-day-new year-duration) ;; end-date
                  )
                  (if (zero? (modulo (+ year 1) 4)) ;; is the next year a long year? does it have a 29th Feb?
                    (if (> (car passed-days-in-year-before-start-day) 58)
                      (+ start-day-new year-duration) ;; end-date
                      (+ start-day-new -1 year-duration) ;; end-date
                    )
                    (+ start-day-new -1 year-duration) ;; end-date
                  )
                )  
              )

              (gnc:debug DBG_1 
                         " get-account-performance"
                         " Fiscal Year: " year
                         " : " (gnc-print-date (cons start-day-new 0)) 
                         " - " (gnc-print-date (cons end-day-new 0))
              )

              ;; ------------------------------------------------------
              ;; add the financial the result of this fiscal 
              ;; year to the result list of this account
              ;; ------------------------------------------------------
	      (set! result-list-account
	        (cons
                  (list
                    year
                    (get-gnc-balance account (cons start-day-new 0))
                    (get-gnc-balance account start-date)
	            (list
	              (getInFlowOfType  ;; cddr element 1: inflow for current year
                        account 
	                accountlist
                        start-day-new ;;  start-date
		        (if (> end-day-new (car end-date)) ;; end-date
		          (car end-date)
		          end-day-new
                        )
                        ACCT-TYPE-BANK
                      )
                      (account-get-balance-intervall  ;; cddr element 2: balance for current year
                        account
                        (cons start-day-new 0) ;; start-date
		        (if (> end-day-new (car end-date))  ;; end-date
		          end-date
		          (cons end-day-new 0)
                        )
                        #f ;; include children?
                      )
	              (getInFlowOfType  ;; cddr element 3: inflow from start until current year
                        account 
	                accountlist
                        (car start-date) ;; start-date
		        (if (> end-day-new (car end-date)) ;; end-date
		          (car end-date)
		          end-day-new
                        )
                        ACCT-TYPE-BANK
                      )
                      (account-get-balance-intervall  ;; cddr element 4: balance from start until current year
                        account 
                        start-date ;; start date
		        (if (> end-day-new (car end-date)) ;; end-date
		          end-date
		          (cons end-day-new 0)
                        )
                        #f ;; include children?
                      )
                    )
                  )
                  result-list-account
                )
              )
	    )
            ;; -------------------------------------------------
            ;; repeat for all fiscal years
            ;; -------------------------------------------------
	    list-of-years
	  )

          (gnc:debug DBG_1
                     " get-account-performance"
                     " Account: " (gnc-account-get-full-name account)
          )
          (gnc:debug DBG_1 
                     " get-account-performance"
                     " Result List: " result-list-account
          )


          ;; ------------------------------------------------------
          ;; merge the financial result of all years for this 
          ;; account into the result of all years for all accounts
          ;; ------------------------------------------------------
          (set! result-list-total
	    (if (eq? result-list-total '())
	      result-list-account
	      (merge-result-lists result-list-account result-list-total)
            )
          )
          ;; ------------------------------------------------------
          ;; prepare for next loop
          ;; ------------------------------------------------------
	  (set! result-list-account '())
        )
        ;; -------------------------------------------------
        ;; repeat for all selected accounts
        ;; -------------------------------------------------
        accountlist
      )

      (gnc:debug DBG_1 
                 " get-account-performance"
                 " Total Result List " result-list-total
      )

      (get-benchmark-list result-list-total result-corridor) ;; RETURN value
    )
  )
)

;; ------------------------------------------------------------------
;; Render the HTML document
;; ------------------------------------------------------------------

(define (document-renderer report-obj)

  ;; -------------------------------------
  ;; helper funtion to access the options
  ;; -------------------------------------
  (define (get-option-val pagename optname)
    (gnc:option-value
     (gnc:lookup-option 
      (gnc:report-options report-obj) pagename optname)))

  (let* (
          ;; ------------------------------------------
          ;; read out other options
          ;; ------------------------------------------

          (account-list (get-option-val gnc:pagename-accounts (N_"Accounts")))

          ;; ------------------------------------------
          ;; init output parameters
          ;; ------------------------------------------

          (document (gnc:make-html-document))
        )

        ;; ------------------------------------------
        ;; start the output document
        ;; ------------------------------------------

        (gnc:html-document-set-title! document (N_ "GnuCash Performance Report"))
      
        (gnc:html-document-set-style!
          document 
	  "body" 
          'attribute (list "bgcolor" "#f6ffdb")
        )

        ;; ------------------------------------------
        ;; start evaluating the options
        ;; ------------------------------------------

        (if (null? account-list)
          (gnc:html-document-add-object!
            document
            (gnc:make-html-text
              (gnc:html-markup-p (N_ "No accounts selected."))
            )
          )
          ;; else
          (let 
            (
              (boolean-show-graph (get-option-val gnc:pagename-display (N_ "Show Result Graph")))
              (boolean-show-table (get-option-val gnc:pagename-display (N_ "Show Result Table")))
            )
         
            (if (not (or boolean-show-graph boolean-show-table))
              (gnc:html-document-add-object!
                document
                (gnc:make-html-text
                  (gnc:html-markup-p 
                    (N_ 
                    "You have selected to not display anything. \
                     Go to the report options and enable tabular and/or graphical result display."))
                )
              )
              ;; else
              (let* 
                (
                  (start-date-val 
                    (gnc:date-option-absolute-time
                      (get-option-val gnc:pagename-general (N_ "Start of Benchmark Period"))
                    )
                  )
                  (end-date-val 
                    (gnc:date-option-absolute-time
                      (get-option-val gnc:pagename-general (N_ "End of Benchmark Period"))
                    )
                  )
                  (result-corridor
                    (list
                      (get-option-val gnc:pagename-general (N_ "Expected Result Corridor - Upper Limit"))
                      (get-option-val gnc:pagename-general (N_ "Expected Result Corridor - Lower Limit"))
                    )
                  )
                  (benchmark-list 
                    (get-account-performance 
                      account-list 
                      start-date-val 
                      end-date-val 
                      result-corridor
                    )
                  ) ;; run the report algorithm
                )
  
                ;; ------------------------------------------
                ;; list selected accounts
                ;; ------------------------------------------
                (gnc:html-document-add-object!
                  document
                  (gnc:make-html-text
                    (gnc:html-markup-p (N_ "Selected Accounts:"))
                    (gnc:html-markup-ul
                      (map 
                        (lambda (acct)
                          (gnc:html-markup-anchor 
                            (gnc-build-url 
                              URL-TYPE-REGISTER
		              (string-append "account=" (gnc-account-get-full-name acct))
                              ""
                            )
                            (gnc-account-get-full-name acct)
                          )
                        )
                        account-list
                      )
                    )
                  )
                )
  
                ;; ------------------------------------------
                ;; show the fiscal period
                ;; ------------------------------------------
                (gnc:html-document-add-object!
                  document
                  (gnc:make-html-text
                    (gnc:html-markup-p 
                      (_ 
                        (string-append
                          "Fiscal Period: "
                          (gnc-print-date start-date-val )
                          " to "
                          (gnc-print-date end-date-val )
  	                )
                      )
                    )
                  )
                )
  
                ;; ------------------------------------------
                ;; show the result corridor
                ;; ------------------------------------------
                (gnc:html-document-add-object!
                  document
                  (gnc:make-html-text
                    (gnc:html-markup-p 
                      (_ 
                        (string-append
                          "Result Corridor: "
                          (number->string (cadr result-corridor))
                          "% to "
                          (number->string (car result-corridor))
                          "%"
  	                )
                      )
                    )
                  )
                )

                ;; ------------------------------------------
                ;; show the result in tabular format
                ;; ------------------------------------------
                (if boolean-show-table
                  (let 
                    (
                      (table (gnc:make-html-table))
                    )
                    (gnc:html-document-add-object!
                      document
                      (gnc:make-html-text
                        (gnc:html-markup-p (N_ "Benchmark Result:"))
                      )
                    )
                    (gnc:html-table-append-column! 
                      table 
                      (list (N_ "Year") (N_ "Single Year Result") (N_ "Accumulated Result"))
                    )
                    (for-each
                      (lambda (year-result)
                        (gnc:html-table-append-column! 
                          table 
                          (map number->string year-result)
                        )
                      )
                     benchmark-list
                    )
                    ;;(gnc:html-table-set-caption! 
                    ;;  table 
                    ;;  (_ "Benchmark Result")
                    ;;)
                    (gnc:html-document-add-object! document table)
                  )
                  ;; else
                  (gnc:html-document-add-object!
                    document
                    (gnc:make-html-text
                      (gnc:html-markup-p (N_ "You have chosen not to display the result in tabular format."))
                    )
                  )
                )

                ;; ------------------------------------------
                ;; show the result graphical format
                ;; ------------------------------------------
                (if boolean-show-graph
                  (let 
                    (
                      (chart (gnc:make-html-linechart))
                      (row-label-list (list))
                      (yearly-result-list (list))
                      (accumulated-result-list (list))
                    )
        
                    (gnc:html-linechart-set-title!        chart (N_ "Performance Graph")) ;; optional
                    (gnc:html-linechart-set-subtitle!     chart (N_ "Benchmark Result"))  ;; optional
                    (gnc:html-linechart-set-y-axis-label! chart (N_ "Interest (%)"))	  ;; optional
                    (gnc:html-linechart-set-x-axis-label! chart (N_ "Fiscal Years"))      ;; optional
                    (gnc:html-linechart-set-col-labels!   chart (list (N_ "Single Year") (N_ "Accumulated")))
                    (gnc:html-linechart-set-col-colors!   chart (list (N_"green") (N_ "blue")))
                    (gnc:html-linechart-set-width!        chart 600)
                    (gnc:html-linechart-set-height!       chart 400)

                    (for-each
                      (lambda (year-result)
                        (set! row-label-list
                          (cons
                            (number->string (car year-result))
                            row-label-list
                          )
                        )
                        (set! yearly-result-list
                          (cons
                            (cadr year-result)
                            yearly-result-list
                          )
                        )
                        (set! accumulated-result-list
                          (cons
                            (caddr year-result)
                            accumulated-result-list
                          )
                        )
                      )
                      (reverse benchmark-list)
                    )
      
                    (gnc:html-linechart-set-row-labels! chart row-label-list)
                    (gnc:html-linechart-append-column!  chart yearly-result-list)
                    (gnc:html-linechart-append-column!  chart accumulated-result-list)
      
                    (gnc:html-document-add-object! document chart)
                  )
                  ;; else
                  (gnc:html-document-add-object!
                    document
                    (gnc:make-html-text
                      (gnc:html-markup-p (N_ "You have chosen not to display the result graph."))
                    )
                  )
                )
              )
            )
          )
        )

        (gnc:html-document-add-object!
          document
          (gnc:make-html-text
            (gnc:html-markup-br)
            (gnc:html-markup-i
              (string-append (N_ "Report Creation Date: ") (gnc-print-date (gnc:get-today)))
            )
            (gnc:html-markup-br)
            (gnc:html-markup-i 
              (string-append (N_ "Performance Report Version ") version)
            )
          )
        )

        (if (get-option-val gnc:pagename-display (N_ "Show Purpose"))
          (gnc:html-document-add-object!
            document
            (gnc:make-html-text
              (gnc:html-markup-h3 purposetext-headline)
              (gnc:html-markup-p  purposetext-paragraph01)
              (gnc:html-markup-p  purposetext-paragraph02)
              (gnc:html-markup-p  purposetext-paragraph03)
              (gnc:html-markup-p  purposetext-paragraph04)
              (gnc:html-markup-p  purposetext-paragraph05)
              (gnc:html-markup-p  purposetext-paragraph06)
              (gnc:html-markup-p  purposetext-paragraph07)
              (gnc:html-markup-br)
            )
          )
        )
        
        document ;; RETURN value

  );; end let
);; end define report renderer


;; ------------------------------------------------------------------
;; Define the actual report
;; ------------------------------------------------------------------

(gnc:define-report
 'version 1
 'name (N_ "Performance Report")
 'report-guid "36bf1441d3fa4148bb6aee6dd20bfab9"
 'menu-tip (N_ "Stable. Under verification.")
 'menu-path (list gnc:menuname-asset-liability)
 'options-generator options-generator
 'renderer document-renderer)
