#lang racket/base
(require racket/class
         unstable/class-iop
         racket/list
         data/gvector
         "../base.rkt"
         "interfaces.rkt"
         "cache-box.rkt")
(provide case-result%
         suite-result%)

(define result%
  (class* object% ()
    (super-new)

    (init-field parent
                controller
                name
                test)

    (when parent
      (send/i parent suite<%> add-child this))

    (define/public (get-parent) parent)
    (define/public (get-name) name)
    (define/public (get-controller) controller)
    (define/public (get-test) test)
    ))

;; case-result%
(define case-result%
  (class* result% (case<%>)
    (super-new)

    (inherit get-name
             get-parent
             get-controller)

    ;; *result : #f or test-result
    ;; #f means not finished executing
    (define *result #f)

    (define result #f)
    (define properties #f)
    (define timing #f)
    (define output null)
    (define trash null)

    (define/public (update *result* result* properties* timing* output* trash*)
      (set! *result *result*)
      (set! result result*)
      (set! properties properties*)
      (set! timing timing*)
      (set! output output*)
      (set! trash trash*)
      (send/i (get-controller) controller<%> on-model-status-change this))

    (define/public (finished?) (and *result #t))
    (define/public (success?) (test-success? *result))
    (define/public (failure?) (test-failure? *result))
    (define/public (error?) (test-error? *result))

    (define/public (get-total-cases) 1)
    (define/public (get-total-successes)
      (if (success?) 1 0))
    (define/public (get-total-failures)
      (if (or (failure?) (error?)) 1 0))

    (define/public (get-result) result)
    (define/public (get-timing) timing)
    (define/public (get-trash) trash)
    (define/public (has-trash?) (pair? trash))
    (define/public (get-property p)
      (let [(v (assq p properties))]
        (and v (cdr v))))
    (define/public (get-property-set p)
      (map cdr (filter (lambda (kv) (eq? (car kv) p)) properties)))
    (define/public (get-all-properties)
      properties)

    (define/public (get-output) (reverse output))
    (define/public (has-output?) (pair? output))))


;; suite-result%
(define suite-result%
  (class* result% (suite<%>)
    (super-new)
    (inherit get-name
             get-parent
             get-controller)

    (define done? #f)
    (define children (make-gvector))

    ;; get-children : -> (listof result<%>)
    (define/public (get-children)
      (for/list ([x (in-gvector children)]) x))

    (define/public (add-child c)
      (gvector-add! children c))

    (define/public (finish!)
      (set! done? #t)
      (send/i (get-controller) controller<%> on-model-status-change this))

    (define children-cache
      (cache (for/fold ([cs 0] [ss 0] [fs 0] [out? #f] [trash? #f])
                 ([c (in-gvector children)])
               (values (+ cs (send/i c result<%> get-total-cases))
                       (+ ss (send/i c result<%> get-total-successes))
                       (+ fs (send/i c result<%> get-total-failures))
                       (or out? (send/i c result<%> has-output?))
                       (or trash? (send/i c result<%> has-trash?))))))

    (define/public (finished?)
      done?)
    (define/public (get-total-cases)
      (define-values (c _s _f _o _t) (cache-ref children-cache))
      c)
    (define/public (get-total-successes)
      (define-values (_c s _f _o _t) (cache-ref children-cache))
      s)
    (define/public (get-total-failures)
      (define-values (_c _s f _o _t) (cache-ref children-cache))
      f)
    (define/public (has-output?)
      (define-values (_c _s _f o _t) (cache-ref children-cache))
      o)
    (define/public (has-trash?)
      (define-values (_c _s _f _o t) (cache-ref children-cache))
      t)

    (define/public (success?)
      (and (finished?) (zero? (get-total-failures))))
    (define/public (failure?)
      (positive? (get-total-failures)))
    (define/public (error?) #f)

    ;; on-child-status-change : model<%> -> void
    (define/public (on-child-status-change child)
      (let ([result
             (call-with-values (lambda () (cache-ref children-cache)) list)])
        (cache-invalidate! children-cache)
        (let ([new-result
               (call-with-values (lambda () (cache-ref children-cache)) list)])
          (unless (equal? new-result result)
            (send/i (get-controller) controller<%> on-model-status-change this)))))))
