#lang racket/base
(provide generate-codecov-coverage)
(require
  racket/file
  racket/function
  racket/list
  racket/string
  racket/unit
  json
  net/http-client
  net/uri-codec
  cover/private/file-utils
  "ci-service.rkt"
  "travis-service.rkt")

(module+ test
  (require rackunit cover racket/runtime-path))

;; ->
;; Submit cover information to Codecov
(define (generate-codecov-coverage coverage files [_dir "coverage"])
  (define json (codecov-json coverage files))
  (define-values (status resp port) (send-codecov! json))
  (displayln status)
  (displayln resp)
  (displayln "Coverage information sent to Codecov."))

(define (codecov-json coverage files)
  (hasheq 'messages (hasheq)
          'coverage (calculate-line-coverage coverage files)))

(define (calculate-line-coverage coverage files)
  (for/hasheq ([file (in-list files)])
    (define local-file (string->symbol (path->string (->relative file))))
    (values local-file (line-coverage coverage file))))

;; Coverage PathString Covered? -> [Listof CoverallsCoverage]
;; Get the line coverage for the file to generate a coverage report
(define (line-coverage coverage file)
  (define covered? (curry coverage file))
  (define split-src (string-split (file->string file) "\n"))
  (define (process-coverage value rst-of-line)
    (case (covered? value)
      ['covered (if (equal? 'uncovered rst-of-line) rst-of-line 'covered)]
      ['uncovered 'uncovered]
      [else rst-of-line]))

  (define-values (line-cover _)
    (for/fold ([coverage `(,(json-null))] [count 1]) ([line (in-list split-src)])
      (cond [(zero? (string-length line)) (values (cons (json-null) coverage) (add1 count))]
            [else (define nw-count (+ count (string-length line) 1))
                  (define all-covered (foldr process-coverage 'irrelevant (range count nw-count)))
                  (values (cons (process-coverage-value all-covered) coverage) nw-count)])))
  (reverse line-cover))

(module+ test
  (define-runtime-path path "tests/test-not-run.rkt")
  (let ()
    (parameterize ([current-cover-environment (make-cover-environment)])
      (define file (path->string (simplify-path path)))
      (test-files! file)
      (check-equal? (line-coverage (get-test-coverage) file) `(,(json-null) 1 0)))))

;; CoverageData -> [Or Number json-null]
;; Converts CoverageData to coverage value recognized by Codecov
(define (process-coverage-value value)
  (case value
    ['covered 1]
    ['uncovered 0]
    [else (json-null)]))

;; Send Codecov data
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define services
  (hash travis-ci? travis-service@))

(define CODECOV_HOST "codecov.io")

(define (send-codecov! json)
  (define service (for/first ([(pred unit) services] #:when (pred)) unit))
  (cond [(not service) (error "Failed to find a service.")]
        [else
          (define-values/invoke-unit service (import) (export ci-service^))
          (define raw-params (filter cdr (query)))
          (define params (alist->form-urlencoded raw-params))
          (http-sendrecv CODECOV_HOST
                         (string-append "/upload/v1?" params)
                         #:method "POST"
                         #:ssl? #t
                         #:data (jsexpr->bytes json)
                         #:headers '("Accept: application/json" "Content-Type: application/json"))]))