#lang racket/base
(require "core.rkt" 
         "base-render.rkt"
         "private/render-utils.rkt"
         racket/class racket/port racket/list racket/string
         scribble/text/wrap)
(provide render-mixin)

(define current-preserve-spaces (make-parameter #f))

(define current-indent (make-parameter 0))
(define (make-indent amt)
  (+ amt (current-indent)))
(define (indent)
  (define i (current-indent))
  (unless (zero? i) (display (make-string i #\space))))
(define (indented-newline)
  (newline)
  (indent))

(define render-mixin
  (mixin (render<%>) ()

    (define/override (current-render-mode)
      '(text))

    (define/override (get-substitutions)
      '((#rx"---" "\U2014")
        (#rx"--" "\U2013")
        (#rx"``" "\U201C")
        (#rx"''" "\U201D")
        (#rx"'" "\U2019")))

    (inherit render-block
             format-number)

    (define/override (render-part d ht)
      (let ([number (collected-info-number (part-collected-info d ht))])
        (unless (part-style? d 'hidden)
          (let ([s (format-number number '() #t)])
            (unless (null? s)
              (printf "~a~a" 
                      (car s)
                      (if (part-title-content d)
                          " "
                          "")))
            (when (part-title-content d)
              (render-content (part-title-content d) d ht))
            (when (or (pair? number) (part-title-content d))
              (newline)
              (newline))))
        (render-flow (part-blocks d) d ht #f)
        (let loop ([pos 1]
                   [secs (part-parts d)]
                   [need-newline? (pair? (part-blocks d))])
          (unless (null? secs)
            (when need-newline? (newline))
            (render-part (car secs) ht)
            (loop (add1 pos) (cdr secs) #t)))))

    (define/override (render-flow f part ht starting-item?)
      (if (null? f)
          null
          (append*
           (render-block (car f) part ht starting-item?)
           (for/list ([p (in-list (cdr f))])
             (indented-newline)
             (render-block p part ht #f)))))

    (define/override (render-intrapara-block p part ri first? last? starting-item?)
      (unless first? (indented-newline))
      (super render-intrapara-block p part ri first? last? starting-item?))

    (define/override (render-table i part ht inline?)
      (define flowss (table-blockss i))
      (if (null? flowss)
          null
          (let* ([strs (map (lambda (flows)
                              (map (lambda (d)
                                     (if (eq? d 'cont)
                                         d
                                         (let ([o (open-output-string)])
                                           (parameterize ([current-indent 0]
                                                          [current-output-port o])
                                             (render-block d part ht #f))
                                           (regexp-split
                                            #rx"\n"
                                            (regexp-replace #rx"\n$" (get-output-string o) "")))))
                                   flows))
                            flowss)]
                 [extract-align
                  (lambda (s)
                    (define p (style-properties s))
                    (cond
                     [(member 'right p) 'right]
                     [(member 'center p) 'center]
                     [else 'left]))]
                 [alignss
                  (cond
                   [(ormap (lambda (v) (and (table-cells? v) v)) (style-properties (table-style i)))
                    => (lambda (tc)
                         (for/list ([l (in-list (table-cells-styless tc))])
                           (for/list ([s (in-list l)])
                             (extract-align s))))]
                   [(ormap (lambda (v) (and (table-columns? v) v)) (style-properties (table-style i)))
                    => (lambda (tc)
                         (make-list
                          (length flowss)
                          (for/list ([s (in-list (table-columns-styles tc))])
                            (extract-align s))))]
                   [else
                    (if (null? flowss)
                        null
                        (make-list (length flowss) (make-list (length (car flowss)) 'left)))])]
                 [extract-border
                  (lambda (s)
                    (define p (style-properties s))
                    (cond
                     [(memq 'border p) '#(#t #t #t #t)]
                     [else
                      (vector (memq 'left-border p) (memq 'right-border p)
                              (memq 'top-border p) (memq 'bottom-border p))]))]
                 [borderss
                  ;; A border is (vector left? right? top? bottom?)
                  (cond
                   [(ormap (lambda (v) (and (table-cells? v) v)) (style-properties (table-style i)))
                    => (lambda (tc)
                         (for/list ([l (in-list (table-cells-styless tc))])
                           (for/list ([s (in-list l)])
                             (extract-border s))))]
                   [(ormap (lambda (v) (and (table-columns? v) v)) (style-properties (table-style i)))
                    => (lambda (tc)
                         (make-list
                          (length flowss)
                          (for/list ([s (in-list (table-columns-styles tc))])
                            (extract-border s))))]
                   [else
                    (if (null? flowss)
                        null
                        (make-list (length flowss) (make-list (length (car flowss)) '#(#f #f #f #f))))])]
                 [border-left? (lambda (v) (vector-ref v 0))]
                 [border-right? (lambda (v) (vector-ref v 1))]
                 [border-top? (lambda (v) (vector-ref v 2))]
                 [border-bottom? (lambda (v) (vector-ref v 3))]
                 [col-borders ; has only left and right
                  (for/list ([i (in-range (length (car borderss)))])
                    (for/fold ([v '#(#f #f)]) ([borders (in-list borderss)])
                      (define v2 (list-ref borders i))
                      (vector (or (border-left? v) (border-left? v2))
                              (or (border-right? v) (border-right? v2)))))]
                 [widths (map (lambda (col)
                                (for/fold ([d 0]) ([i (in-list col)])
                                  (if (eq? i 'cont)
                                      d
                                      (apply max d (map string-length i)))))
                              (apply map list strs))]
                 [x-length (lambda (col) (if (eq? col 'cont) 0 (length col)))])

            (define (show-row-border prev-borders borders)
              (when (for/or ([prev-border (in-list prev-borders)]
                             [border (in-list borders)])
                      (or (border-bottom? prev-border)
                          (border-top? border)))
                (define-values (end-h-border? end-v-border?)
                  (for/fold ([left-border? #f]
                             [prev-border? #f])
                      ([w (in-list widths)]
                       [prev-border (in-list prev-borders)]
                       [border (in-list borders)]
                       [col-border (in-list col-borders)])
                    (define border? (or (and prev-border (border-bottom? prev-border))
                                        (border-top? border)))
                    (when (or left-border? (border-left? col-border))
                      (display (if (or prev-border? border?) "-" " ")))
                    (display (make-string w (if border? #\- #\space)))
                    (values (border-right? col-border) border?)))
                (when end-h-border?
                  (display (if end-v-border? "-" " ")))
                (newline)))

            (define-values (last-indent? last-borders)
              (for/fold ([indent? #f] [prev-borders #f]) ([row (in-list strs)]
                                                          [aligns (in-list alignss)]
                                                          [borders (in-list borderss)])
                (values
                 (let ([h (apply max 0 (map x-length row))])
                   (let ([row* (for/list ([i (in-range h)])
                                 (for/list ([col (in-list row)])
                                   (if (i . < . (x-length col))
                                       (list-ref col i)
                                       (if (eq? col 'cont)
                                           'cont
                                           ""))))])
                     (for/fold ([indent? indent?]) ([sub-row (in-list row*)]
                                                    [pos (in-naturals)])
                       (when indent? (indent))

                       (when (zero? pos)
                         (show-row-border (or prev-borders (map (lambda (b) '#(#f #f #f #f)) borders))
                                          borders))

                       (define-values (end-border? end-col-border?)
                         (for/fold ([left-border? #f] [left-col-border? #f])
                             ([col (in-list sub-row)]
                              [w (in-list widths)]
                              [align (in-list aligns)]
                              [border (in-list borders)]
                              [col-border (in-list col-borders)])
                           (when (or left-col-border? (border-left? col-border))
                             (display (if (and (or left-border? (border-left? border))
                                               (not (eq? col 'cont)))
                                          "|"
                                          " ")))
                           (let ([col (if (eq? col 'cont) "" col)])
                             (define gap (max 0 (- w (string-length col))))
                             (case align
                               [(right) (display (make-string gap #\space))]
                               [(center) (display (make-string (quotient gap 2) #\space))])
                             (display col)
                             (case align
                               [(left) (display (make-string gap #\space))]
                               [(center) (display (make-string (- gap (quotient gap 2)) #\space))]))
                           (values (border-right? border)
                                   (border-right? col-border))))
                       (when end-col-border?
                         (display (if end-border? "|" " ")))
                       (newline)
                       #t)))
                 borders)))

            (show-row-border last-borders (map (lambda (b) '#(#f #f #f #f)) last-borders))

            null)))

    (define/override (render-itemization i part ht)
      (let ([flows (itemization-blockss i)])
        (if (null? flows)
            null
            (append*
             (begin (printf "* ")
                    (parameterize ([current-indent (make-indent 2)])
                      (render-flow (car flows) part ht #t)))
             (for/list ([d (in-list (cdr flows))])
               (indented-newline)
               (printf "* ")
               (parameterize ([current-indent (make-indent 2)])
                 (render-flow d part ht #f)))))))

    (define/override (render-paragraph p part ri)
      (define o (open-output-string))
      (parameterize ([current-output-port o])
        (super render-paragraph p part ri))
      (define to-wrap (regexp-replace* #rx"\n" (get-output-string o) " "))
      (define lines (wrap-line (string-trim to-wrap) (- 72 (current-indent))))
      (write-string (car lines))
      (for ([line (in-list (cdr lines))])
        (newline) (indent) (write-string line))
      (newline)
      null)

    (define/override (render-content i part ri)
      (if (and (element? i)
               (let ([s (element-style i)])
                 (or (eq? 'hspace s)
                     (and (style? s)
                          (eq? 'hspace (style-name s))))))
          (parameterize ([current-preserve-spaces #t])
            (super render-content i part ri))
          (super render-content i part ri)))

    (define/override (render-nested-flow i part ri starting-item?)
      (define s (nested-flow-style i))
      (unless (memq 'decorative (style-properties s))
        (if (and s (or (eq? (style-name s) 'inset)
                       (eq? (style-name s) 'code-inset)))
            (begin (printf "  ")
                   (parameterize ([current-indent (make-indent 2)])
                     (super render-nested-flow i part ri starting-item?)))
            (super render-nested-flow i part ri starting-item?))))

    (define/override (render-other i part ht)
      (cond
        [(symbol? i)
         (display (case i
                    [(mdash) "\U2014"]
                    [(ndash) "\U2013"]
                    [(ldquo) "\U201C"]
                    [(rdquo) "\U201D"]
                    [(lsquo) "\U2018"]
                    [(rsquo) "\U2019"]
                    [(lang) ">"]
                    [(rang) "<"]
                    [(rarr) "->"]
                    [(nbsp) "\uA0"]
                    [(prime) "'"]
                    [(alpha) "\u03B1"]
                    [(infin) "\u221E"]
                    [else (error 'text-render "unknown element symbol: ~e" i)]))]
        [(string? i) (if (current-preserve-spaces)
                         (display (regexp-replace* #rx" " i "\uA0"))
                         (display i))]
        [else (write i)])
      null)

    (super-new)))
