;; -*-scheme-*-

;; ------------------------------------------------------------------
;; 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.9")

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

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

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

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

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

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

(define usagetext-paragraph07 "NOTE: Deposits must come from a BANK account (meaning, accounts that have been set to type BANK in GnuCash). This is to cover the case that you get e.g. payments from the state into the selected account as for public subsidized programs like old age pension saving plans, and which should be booked in corrsponding income accounts. Only payments from the bank accounts are seen as personal contribution, 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 Usage") 
            "h" 
            (N_ "Display the help text 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) ;; -50.0
                    )
                  ) ;; 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 "Accounts"))

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

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

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

        (gnc:html-document-set-title! document (_ "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 (_ "You have selected no accounts."))
            )
          )
          ;; else
          (let 
            (
              (boolean-show-graph (get-option-val gnc:pagename-display "Show Result Graph"))
              (boolean-show-table (get-option-val gnc:pagename-display "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 (_ "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 "Start of Benchmark Period")
                    )
                  )
                  (end-date-val 
                    (gnc:date-option-absolute-time
                      (get-option-val gnc:pagename-general "End of Benchmark Period")
                    )
                  )
                  (result-corridor
                    (list
                      (get-option-val gnc:pagename-general "Expected Result Corridor - Upper Limit")
                      (get-option-val gnc:pagename-general "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 (_ "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 (_ "Benchmark Result:"))
                      )
                    )
                    (gnc:html-table-append-column! 
                      table 
                      '("Year" "Single Year Result" "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 (_ "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 "Performance Graph") ;; optional
                    (gnc:html-linechart-set-subtitle!     chart "Benchmark Result")	 ;; optional
                    (gnc:html-linechart-set-y-axis-label! chart "Interest (%)")	 ;; optional
                    (gnc:html-linechart-set-x-axis-label! chart "Fiscal Years")      ;; optional
                    (gnc:html-linechart-set-col-labels!   chart '("Single Year" "Accumulated"))
                    (gnc:html-linechart-set-col-colors!   chart '("green" "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 (_ "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 "Report Creation Date: " (gnc-print-date (gnc:get-today)))
              )
            )
            (gnc:html-markup-br)
            (gnc:html-markup-i 
              (_ 
                (string-append "Performance Report Version " version)
              )
            )
          )
        )

        (if (get-option-val gnc:pagename-display "Show Usage")
          (gnc:html-document-add-object!
            document
            (gnc:make-html-text
              (gnc:html-markup-h3 (_ usagetext-headline))
              (gnc:html-markup-p  (_ usagetext-paragraph01))
              (gnc:html-markup-p  (_ usagetext-paragraph02))
              (gnc:html-markup-p  (_ usagetext-paragraph03))
              (gnc:html-markup-p  (_ usagetext-paragraph04))
              (gnc:html-markup-p  (_ usagetext-paragraph05))
              (gnc:html-markup-p  (_ usagetext-paragraph06))
              (gnc:html-markup-p  (_ usagetext-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)
