;;======================================================================
;; Copyright 2006-2013, Matthew Welland.
;; 
;; This file is part of Megatest.
;; 
;;     Megatest is free software: you can redistribute it and/or modify
;;     it under the terms of the GNU General Public License as published by
;;     the Free Software Foundation, either version 3 of the License, or
;;     (at your option) any later version.
;; 
;;     Megatest is distributed in the hope that it will be useful,
;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;     GNU General Public License for more details.
;; 
;;     You should have received a copy of the GNU General Public License
;;     along with Megatest.  If not, see <http://www.gnu.org/licenses/>.
;;
;;======================================================================

	  
;; for each test:
;;   
(define (tests:filter-non-runnable run-id testkeynames testrecordshash)
  (let ((runnables '()))
    (for-each
     (lambda (testkeyname)
       (let* ((test-record (hash-table-ref testrecordshash testkeyname))
	      (test-name   (tests:testqueue-get-testname  test-record))
	      (itemdat     (tests:testqueue-get-itemdat   test-record))
	      (item-path   (tests:testqueue-get-item_path test-record))
	      (waitons     (tests:testqueue-get-waitons   test-record))
	      (keep-test   #t)
	      (test-id     (rmt:get-test-id run-id test-name item-path))
	      (tdat        (rmt:get-testinfo-state-status run-id test-id))) ;; (cdb:get-test-info-by-id *runremote* test-id)))
	 (if tdat
	     (begin
	       ;; Look at the test state and status
	       (if (or (and (member (db:test-get-status tdat) 
				    '("PASS" "WARN" "WAIVED" "CHECK" "SKIP"))
			    (equal? (db:test-get-state tdat) "COMPLETED"))
		       (member (db:test-get-state tdat)
				    '("INCOMPLETE" "KILLED")))
		   (set! keep-test #f))

	       ;; examine waitons for any fails. If it is FAIL or INCOMPLETE then eliminate this test
	       ;; from the runnable list
	       (if keep-test
		   (for-each (lambda (waiton)
			       ;; for now we are waiting only on the parent test
			       (let* ((parent-test-id (rmt:get-test-id run-id waiton ""))
				      (wtdat          (rmt:get-testinfo-state-status run-id test-id))) ;; (cdb:get-test-info-by-id *runremote* test-id)))
				 (if (or (and (equal? (db:test-get-state wtdat) "COMPLETED")
					      (member (db:test-get-status wtdat) '("FAIL" "ABORT")))
					 (member (db:test-get-status wtdat)  '("KILLED"))
					 (member (db:test-get-state wtdat)   '("INCOMPETE")))
				 ;; (if (or (member (db:test-get-status wtdat)
				 ;;        	 '("FAIL" "KILLED"))
				 ;;         (member (db:test-get-state wtdat)
				 ;;        	 '("INCOMPETE")))
				     (set! keep-test #f)))) ;; no point in running this one again
			     waitons))))
	 (if keep-test (set! runnables (cons testkeyname runnables)))))
     testkeynames)
    runnables))

;;======================================================================
;; html output from server
;;======================================================================


(define (tests:dynamic-dboard page)
;(define (tests:create-html-tree o)
 (let* (
;(page "1")
          (linktree  (common:get-linktree))
         (area-name (common:get-testsuite-name))
	       (keys      (rmt:get-keys))
	       (numkeys   (length keys))
         (targtweaked (make-list numkeys "%"))
         (target-patt (string-join targtweaked "/"))
         (total-runs  (rmt:get-num-runs "%"))
         (pg-size 10)
         (pg (if (equal? page #f)
                 0
                 (- (string->number page) 1)))
          (get-prev-links  (lambda (pg linktree)
                           (debug:print-info 0 *default-log-port* "val: " (- 1 pg))
                          (let* ((link  (if (not (eq? pg 0))
                               (s:a  "&lt;&lt;prev " 'href (conc  "dashboard?page="  pg  ))
                               (s:a "" 'href (conc  "dashboard?page=" pg)))))
                               link)))
          (get-next-links   (lambda (pg linktree total-runs)  
                            (debug:print-info 0 *default-log-port* "val: " pg)
                             (debug:print-info 0 *default-log-port* "val: " total-runs " size" pg-size)
 
                            (let* ((link  (if (> total-runs (+ 10 (* pg pg-size)))
                              (s:a  "next&gt;&gt; "  'href (conc  "dashboard?page="  (+ pg 2)  ))
                             (s:a "" 'href (conc  "dashboard?page=" pg  )))))
                             link)))
         (html-body (tests:dashboard-body pg pg-size keys numkeys total-runs linktree area-name get-prev-links get-next-links #t "%" target-patt))) ;; update tis function
        html-body))

(define (tests:create-html-summary outf)
 (let* ((lockfile  (conc outf ".lock"))
        (linktree  (common:get-linktree))
				(keys      (rmt:get-keys))
        (area-name (common:get-testsuite-name))
        (run-patt (or (args:get-arg "-run-patt")
                        (args:get-arg "-runname")
                        "%"))
        (target (or (args:get-arg "-target-patt")
                        (args:get-arg "-target")
                        "%"))
         (targlist (string-split target "/"))
         (numkeys  (length keys))
	       (numtarg  (length targlist))  
         (targtweaked (if (> numkeys numtarg)
			   								(append targlist (make-list (- numkeys numtarg) "%"))
			  								targlist))
        (target-patt (string-join targtweaked "/")))
    (if (common:simple-file-lock lockfile)
        (begin
          (let* (;(runsdat1   (rmt:get-runs run-patt #f #f (map (lambda (x)(list x "%")) keys)))
                 (runsdat   (rmt:get-runs-by-patt  keys run-patt target-patt #f #f #f 0))
					       (runs      (vector-ref runsdat 1))
                 (header      (vector-ref runsdat 0))
        	       (oup       (open-output-file (or outf (conc linktree "/targets.html"))))
                 (target-hash (test:create-target-hash runs header (length keys))))
           (test:create-target-html target-hash oup area-name linktree)
          (test:create-run-html  runs area-name linktree (length keys) header))
	  (common:simple-file-release-lock lockfile))
	#f)))

(define (test:get-test-hash test-data)
	(let ((resh (make-hash-table)))
    	(map (lambda (test)
        (let* ((test-name (vector-ref test 2))
               (test-html-path (if (file-exists? (conc (vector-ref test 10) "/test-summary.html"))
																 (conc (vector-ref test 10) "/test-summary.html" )
							 									 (conc (vector-ref test 10) "/" (vector-ref test 13))))
               (test-item  (vector-ref test 11))
               (test-status (vector-ref test 4)))
               (if (not (hash-table-ref/default resh test-item  #f))
                   (hash-table-set! resh test-item   (make-hash-table)))
               (hash-table-set! (hash-table-ref/default resh test-item  #f) test-name (list test-status test-html-path)))) 
        test-data)
resh))

(define (test:get-data->b-keys ordered-data a-keys)
  (delete-duplicates
   (sort (apply
	  append
	  (map (lambda (sub-key)
		 (let ((subdat (hash-table-ref ordered-data sub-key)))
		   (hash-table-keys subdat)))
	       a-keys))
	 string>=?)))


(define (test:create-run-html runs area-name linktree numkeys header)
  (map (lambda (run)
		 (let* ((target (string-join (take (vector->list run) numkeys) "/"))
						(run-name (db:get-value-by-header run header "runname"))
            (run-time (seconds->work-week/day-time (db:get-value-by-header run header "event_time")))
						(oup (if (file-exists? (conc linktree "/" target "/" run-name))
                        (open-output-file (conc linktree "/" target "/" run-name "/run.html"))
                         #f))
            (run-id (db:get-value-by-header run header "id"))
            (test-data    (rmt:get-tests-for-run
				  								 run-id
                           "%"       ;; testnamepatt
				  								 '()        ;; states
				   								 '()        ;; statuses
				  								 	#f         ;; offset
				  						 			#f         ;; num-to-get
				   									#f         ;; hide/not-hide
				  								  #f         ;; sort-by
				   									#f         ;; sort-order
				   									#f         ;; 'shortlist                           ;; qrytype
                            0         ;; last update
				  									#f))
            (item-test-hash (test:get-test-hash test-data))
            (items  (hash-table-keys item-test-hash))
 						(test-names (test:get-data->b-keys item-test-hash items)))
    (if oup
      (begin 
     (s:output-new
	   oup
	   (s:html tests:css-jscript-block (tests:css-jscript-block-cond #f)
		   (s:title "Runs View " run-name)
		   (s:body
		     (s:h1 "Runs View " )
         (s:h3 "Target" target)
				 (s:p 
					(s:b "Run name" ) run-name)
         (s:p 
					(s:b "Run Date" ) run-time)
         (s:table 'border 1 'cellspacing 0
           (s:tr
           (s:th "Items")
           (map (lambda (test)
            (s:th test))
           test-names))  
           (map (lambda (item) 
					  (let* ((test-hash (hash-table-ref/default item-test-hash item  #f)))
								 (if test-hash
                  (begin
									(s:tr
					  			(s:td 'class "test" item)
            			(map (lambda (test)
						  		(let* ((test-details (hash-table-ref/default test-hash test  #f))
												(status (if test-details
																(car test-details)))
                        (link (if test-details 
														(string-substitute  (conc linktree "/" target "/" run-name "/")  "" (cadr test-details) "-"))))
                   (if test-details
											(s:td 'class status
												(s:a 'class "link" 'href link status ))
                      (s:td "")))) 			
									test-names))))))
				  (sort items string<=?))))))
		(close-output-port oup))
    (debug:print-info 0 "Skip: Dirctory structure " linktree "/" target "/" run-name " does not exist. Megatest will not create run.html"))))
runs))

(define (test:create-target-hash runs header numkeys)
  (let ((resh (make-hash-table)))
   (for-each
     (lambda (run)
        (let* ((run-name (db:get-value-by-header run header "runname"))
               (target   (string-join (take (vector->list run) numkeys) "/"))
               (run-list (hash-table-ref/default resh target  #f)))
               
               (if (not run-list)
                   (hash-table-set! resh target   (list run-name))
                   (hash-table-set! resh target   (cons run-name run-list)))))
      runs)
   resh))

(define (test:get-max-run-cnt target-hash targets)
   (let* ((cnt 0 ))
   (map (lambda (target)
        (let* ((runs  (hash-table-ref/default target-hash target  #f))
               (run-length (if runs
																(length runs)
                                 0)))
  
              (if (< cnt run-length)
               (set! cnt  run-length)))) 
		targets) 
cnt))
 
(define (test:pad-runs target-hash targets max-row-length)
 (map (lambda (target)
        (let loop ((run-list  (hash-table-ref/default target-hash target  #f)))
               (if (< (length run-list) max-row-length)
                 (begin  
               		 (hash-table-set! target-hash target   (cons "" run-list))
               		 (loop (hash-table-ref/default target-hash target  #f) ))))) 
		targets)
   target-hash)

(define (test:create-target-html target-hash oup area-name linktree)
  (let* ((targets (hash-table-keys target-hash))
         (max-row-length (test:get-max-run-cnt target-hash targets))
         (pad-runs-hash (test:pad-runs target-hash targets max-row-length)))
   (s:output-new
	   oup
	   (s:html tests:css-jscript-block (tests:css-jscript-block-cond #f)

		   (s:title "Target View " area-name)
		   (s:body
		   (s:h1 "Target View " area-name)
					(s:table 'id "LinkedList1" 'border "1" 'cellspacing 0
             (s:tr 'class "something" 
               (s:th "Target")
								(s:th 'colspan max-row-length "Runs"))                                              
                (let* ((tbl (map (lambda (target)
                      (s:tr
                      (s:td 'class "test" target)
										  (let* ((runs  (hash-table-ref/default target-hash target  #f))
														 (rest-row (map (lambda (run)
																				(if (equal? run "")
																						(s:td run)
                                            (if (file-exists?(conc linktree "/" target "/" run ))
																						(begin 
																							(s:td 
																							(s:a 'href (conc  target "/" run "/run.html") run))))))
																				(reverse runs))))
                              rest-row)))
                                   targets)))
                           tbl)))))
          (close-output-port oup)))


(define (tests:create-html-tree-old outf)
   (let* ((lockfile  (conc outf ".lock"))
	 (runs-to-process '()))
    (if (common:simple-file-lock lockfile)
	(let* ((linktree  (common:get-linktree))
	       (oup       (open-output-file (or outf (conc linktree "/runs-index.html"))))
	       (area-name (common:get-testsuite-name))
	       (keys      (rmt:get-keys))
	       (numkeys   (length keys))
	       (runsdat   (rmt:get-runs "%" #f #f (map (lambda (x)(list x "%")) keys)))
	       (header    (vector-ref runsdat 0))
	       (runs      (vector-ref runsdat 1))
	       (runtreedat (map (lambda (x)
				  (tests:run-record->test-path x numkeys))
				runs))
	       (runs-htree (common:list->htree runtreedat)))
	  (set! runs-to-process runs)
	  (s:output-new
	   oup
	   (s:html tests:css-jscript-block
		   (s:title "Summary for " area-name)
		   (s:body 'onload "addEvents();"
			   (s:h1 "Summary for " area-name)
			   ;; top list
			   (s:ul 'id "LinkedList1" 'class "LinkedList"
				 (s:li
				  "Runs"
				  (common:htree->html runs-htree
						      '()
						      (lambda (x p)
							(let* ((targ-path (string-intersperse p "/"))
                                                               (full-path (conc linktree "/" targ-path))
                                                               (run-name  (car (reverse p))))
                                                          (if (and (common:file-exists? full-path)
                                                                   (directory?   full-path)
                                                                   (file-writable? full-path))
                                                              (s:a run-name 'href (conc targ-path "/run-summary.html"))
                                                              (begin
                                                                (debug:print 0 *default-log-port* "INFO: Can't create " targ-path "/run-summary.html")
                                                                (conc run-name " (Not able to create summary at " targ-path ")")))))))))))
          (close-output-port oup)
	  (common:simple-file-release-lock lockfile)
               
	  (for-each
	   (lambda (run)
	     (let* ((test-subpath (tests:run-record->test-path run numkeys))
		    (run-id       (db:get-value-by-header run header "id"))
                    (run-dir      (tests:run-record->test-path run numkeys))
		    (test-dats    (rmt:get-tests-for-run
				   run-id
                                   "%/"       ;; testnamepatt
				   '()        ;; states
				   '()        ;; statuses
				   #f         ;; offset
				   #f         ;; num-to-get
				   #f         ;; hide/not-hide
				   #f         ;; sort-by
				   #f         ;; sort-order
				   #f         ;; 'shortlist                           ;; qrytype
                                   0         ;; last update
				   #f))
                    (tests-tree-dat (map (lambda (test-dat)
                                         ;; (tests:run-record->test-path x numkeys))
                                         (let* ((test-name  (db:test-get-testname test-dat))
                                                (item-path  (db:test-get-item-path test-dat))
                                                (full-name  (db:test-make-full-name test-name item-path))
                                                (path-parts (string-split full-name)))
                                           path-parts))
                                       test-dats))
                    (tests-htree (common:list->htree tests-tree-dat))
                    (html-dir    (conc linktree "/" (string-intersperse run-dir "/")))
                    (html-path   (conc html-dir "/run-summary.html"))
                    (oup         (if (and (common:file-exists? html-dir)
                                          (directory?   html-dir)
                                          (file-writable? html-dir))
                                     (open-output-file  html-path)
                                     #f)))
               ;; (print "run-dir: " run-dir ", tests-tree-dat: " tests-tree-dat)
               (if oup
                   (begin
                     (s:output-new
                      oup
                      (s:html tests:css-jscript-block
                              (s:title "Summary for " area-name)
                              (s:body 'onload "addEvents();"
                                      (s:h1 "Summary for " (string-intersperse run-dir "/"))
                                      ;; top list
                                      (s:ul 'id "LinkedList1" 'class "LinkedList"
                                            (s:li
                                             "Tests"
                                             (common:htree->html tests-htree
                                                                 '()
                                                                 (lambda (x p)
                                                                   (let* ((targ-path (string-intersperse p "/"))
                                                                          (test-name (car p))
                                                                          (item-path ;; (if (> (length p) 2) ;; test-name + run-name
                                                                           (string-intersperse p "/"))
                                                                          (full-targ (conc html-dir "/" targ-path))
                                                                          (std-file  (conc full-targ "/test-summary.html"))
                                                                          (alt-file  (conc full-targ "/megatest-rollup-" test-name ".html"))
                                                                          (html-file (if (common:file-exists? alt-file)
                                                                                         alt-file
                                                                                         std-file))
                                                                          (run-name  (car (reverse p))))
                                                                     (if (and (not (common:file-exists? full-targ))
                                                                              (directory? full-targ)
                                                                              (file-writable? full-targ))
                                                                         (tests:summarize-test 
                                                                          run-id 
                                                                          (rmt:get-test-id run-id test-name item-path)))
                                                                     (if (common:file-exists? full-targ)
                                                                         (s:a run-name 'href html-file)
                                                                         (begin
                                                                           (debug:print 0 *default-log-port* "ERROR: can't access " full-targ)
                                                                           (conc "No summary for " run-name)))))
                                                                 ))))))
                     (close-output-port oup)))))
           runs)
          #t)
	#f)))

;;======================================================================
;; web pages
;;======================================================================

(define (http-transport:html-test-log $)
   (let* ((run-id ($ 'runid))
         (test-item ($ 'testname))
         (parts (string-split test-item ":"))
         (test-name (car parts))
             
         (item-name (if (equal? (length parts) 1)
             ""
             (cadr parts))))
					;(print $) 
     (tests:get-test-log run-id test-name item-name)))


(define (http-transport:html-dboard $)
  (let* ((page ($ 'page))
         (oup       (open-output-string)) 
         (bdy "--------------------------")

         (ret  (tests:dynamic-dboard page)))
    (s:output-new  oup  ret)
   (close-output-port oup)

  (set! bdy   (get-output-string oup))
     (conc "<h1>Dashboard</h1>" bdy "<br/> <br/> "  )))

(define (http-transport:main-page)
  (let ((linkpath (root-path)))
    (conc "<head><h1>" (pathname-strip-directory *toppath*) "</h1></head>"
	  "<body>"
	  "Run area: " *toppath*
	  "<h2>Server Stats</h2>"
	  (http-transport:stats-table) 
	  "<hr>"
	  (http-transport:runs linkpath)
	  "<hr>"
	  ;; (http-transport:run-stats)
	  "</body>"
	  )))

(define (http-transport:stats-table)
  (mutex-lock! *heartbeat-mutex*)
  (let ((res 
	 (conc "<table>"
	       ;; "<tr><td>Max cached queries</td>        <td>" *max-cache-size* "</td></tr>"
	       "<tr><td>Number of cached writes</td>   <td>" *number-of-writes* "</td></tr>"
	       "<tr><td>Average cached write time</td> <td>" (if (eq? *number-of-writes* 0)
								 "n/a (no writes)"
								 (/ *writes-total-delay*
								    *number-of-writes*))
	       " ms</td></tr>"
	       "<tr><td>Number non-cached queries</td> <td>"  *number-non-write-queries* "</td></tr>"
	       ;; "<tr><td>Average non-cached time</td>   <td>" (if (eq? *number-non-write-queries* 0)
	       ;; 							 "n/a (no queries)"
	       ;; 							 (/ *total-non-write-delay* 
	       ;; 							    *number-non-write-queries*))
	       " ms</td></tr>"
	       "<tr><td>Last access</td><td>"              (seconds->time-string *db-last-access*) "</td></tr>"
	       "</table>")))
    (mutex-unlock! *heartbeat-mutex*)
    res))

(define (http-transport:runs linkpath)
  (conc "<h3>Runs</h3>"
	(string-intersperse
	 (let ((files (map pathname-strip-directory (glob (conc linkpath "/*")))))
	   (map (lambda (p)
		  (conc "<a href=\"" p "\">" p "</a><br>"))
		files))
	 " ")))

#;(define (http-transport:run-stats)
  (let ((stats (open-run-close db:get-running-stats #f)))
    (conc "<table>"
	  (string-intersperse
	   (map (lambda (stat)
		  (conc "<tr><td>" (car stat) "</td><td>" (cadr stat) "</td></tr>"))
		stats)
	   " ")
	  "</table>")))

(define (tests:get-test-log run-id test-name item-name)
  (let* ((test-data    (rmt:get-tests-for-run
				   (string->number run-id)
                                    test-name      ;; testnamepatt
				   '()        ;; states
				   '()        ;; statuses
				   #f         ;; offset
				   #f         ;; num-to-get
				   #f         ;; hide/not-hide
				   #f         ;; sort-by
				   #f         ;; sort-order
				   #f         ;; 'shortlist                           ;; qrytype
                                   0         ;; last update
				   #f))
         (path "")
         (found 0))
    (debug:print-info 0 *default-log-port* "found: " found )

   (let loop ((hed (car test-data))
		 (tal (cdr test-data)))
          (debug:print-info 0 *default-log-port* "item: " (vector-ref hed 11) (vector-ref hed 10) "/" (vector-ref hed 13))

	(if (equal? (vector-ref hed 11) item-name)
            (begin
              (set! found 1) 
	      (set! path (conc (vector-ref hed 10) "/" (vector-ref hed 13)))))
	    (if (and (not (null? tal)) (equal? found 0))
		(loop (car tal)(cdr tal))))
   (if (equal? path "")
     "<H2>Data not found</H2>"
     (string-join (tests:readlines path) "\n"))))

;;===============================================
;; Java script
;;===============================================
(define (http-transport:show-jquery)
  (let* ((data  (tests:readlines *java-script-lib*)))
(string-join data "\n")))

