#lang racket/base
(require racket/match
         racket/list
         racket/contract
         "private/pprint.rkt"
         "ast.rkt")

(define format-datum
  (match-lambda
   [(predicate-sym _ s)
    (format-datum s)]
   [(? symbol? s)
    (text (symbol->string s))]
   [(? string? s)
    (text (format "~S" s))]
   [(? number? s)
    (text (format "~S" s))]
   [x
    (text (format "~v" x))]))
(define (format-variable v)
  (format-datum (variable-sym v)))
(define (format-constant c)
  (format-datum (constant-value c)))
(define format-term 
  (match-lambda
    [(? variable? t)
     (format-variable t)]
    [(? constant? t)
     (format-constant t)]))
(define (format-literal l)
  (match l
    [(struct literal (_ pred (list)))
     (format-datum pred)]
    [(struct literal (_ '= (list a b)))
     (h-append (format-term a) space (text "=") space (format-term b))]
    [(struct literal (_ pred terms))
     (h-append (format-datum pred)
               lparen
               (v-concat/s (apply-infix ", " (map format-term terms)))
               rparen)]))
(define format-external
  (match-lambda
    [(external _ pred-sym pred args anss)
     (h-append (format-datum pred-sym)
               lparen
               (v-concat/s (apply-infix ", " (map format-term args)))
               rparen
               (text " = ")
               lparen
               (v-concat/s (apply-infix ", " (map format-term anss)))
               rparen)]))
(define format-question
  (match-lambda
    [(? literal? l)
     (format-literal l)]
    [(? external? e)
     (format-external e)]))
(define (format-questions ls)
  (v-concat
   (map (lambda (l)
          (h-append (format-question l) dot))
        ls)))
(define (format-clause c)
  (if (empty? (clause-body c))
      (format-literal (clause-head c))
      (nest 4
            (v-concat/s
             (list* (h-append (format-literal (clause-head c)) space (text ":-") space)
                    (apply-infix ", " (map format-literal (clause-body c))))))))
(define (format-assertion a)
  (h-append (format-clause (assertion-clause a))
            dot))
(define (format-retraction r)
  (h-append (format-clause (retraction-clause r))
            (char #\~)))
(define (format-query q)
  (h-append (format-question (query-question q))
            (char #\?)))

(define (format-statement s)
  (cond
    [(assertion? s) (format-assertion s)]
    [(retraction? s) (format-retraction s)]
    [(query? s) (format-query s)]))
(define (format-program p)
  (v-concat (map format-statement p)))

(provide/contract
 [format-datum (datum/c . -> . doc?)]
 [format-variable (variable? . -> . doc?)]
 [format-constant (constant? . -> . doc?)]
 [format-term (term/c . -> . doc?)]
 [format-literal (literal? . -> . doc?)]
 [format-questions ((listof question/c) . -> . doc?)]
 [format-clause (clause? . -> . doc?)]
 [format-assertion (assertion? . -> . doc?)]
 [format-retraction (retraction? . -> . doc?)]
 [format-query (query? . -> . doc?)]
 [format-statement (statement/c . -> . doc?)]
 [format-program (program/c . -> . doc?)])
