
(module texpict-extra mzscheme
  (require (lib "unitsig.ss")
	   (lib "etc.ss")
	   (lib "list.ss"))

  (require "texpict-sig.ss"
	   "common-sig.ss")

  (provide texpict-extra@)
  (define texpict-extra@
    (unit/sig ((open texpict-extra^)
	       (open texpict-common-setup^))
      (import ((open texpict-common^)
	       (open texpict-internal^)))

      (define using-pict2e-package
	(make-parameter #f
			(lambda (x)
			  (and x #t))))

      (define use-old-connect
	(make-parameter #f
			(lambda (x)
			  (and x #t))))

      (define output-measure-commands
	(make-parameter #t
			(lambda (x)
			  (and x #t))))

      (define draw-bezier-lines
	(make-parameter #f
			(lambda (x)
			  (if (procedure? x)
			      (begin
				(unless (procedure-arity-includes? x 1)
				  (raise-type-error 'draw-bezier-lines
						    "boolean or procedure of one argument"
						    x))
				x)
			      (and x #t)))))

      (define serialize-tex-picts
	(make-parameter #f
			(lambda (x)
			  (and x #t))))

      (define tex-series-prefix
	(make-parameter #f
			(lambda (s)
			  (when s
			    (unless (string? s)
			      (raise-type-error 'tex-series-prefix "string or #f" s)))
			  s)))

      (define current-tex-sizer
	(make-parameter (lambda (t) #f)))

      (define label-sizes null)
      (define (extract-num s) ; strip off trainling `pt'
	(let ([str (symbol->string s)])
	  (inexact->exact
	   (ceiling
	    (string->number (substring str 0 (- (string-length str) 2)))))))

      (define (read-in-sizes file)
	(parameterize ([read-case-sensitive #t])
	  (when (file-exists? file)
	    (set! label-sizes
		  (append (with-input-from-file file
			    (lambda ()
			      (let loop ()
				(let ([e (read)])
				  (if (eof-object? e)
				      null
				      (let ([w (read)]
					    [h (read)]
					    [d (read)])
					(cons (list e
						    (extract-num w) 
						    (extract-num h)
						    (extract-num d))
					      (loop))))))))
			  label-sizes)))))

      ;; Marshall a tex string into a simple symbol
      (define digits (make-vector 64))
      (let loop ([i 0])
	(unless (= i 10)
	  (vector-set! digits i (integer->char (+ i (char->integer #\0))))
	  (loop (add1 i))))
      (let loop ([i 0])
	(unless (= i 26)
	  (vector-set! digits (+ i 10) (integer->char (+ i (char->integer #\a))))
	  (vector-set! digits (+ i 36) (integer->char (+ i (char->integer #\A))))
	  (loop (add1 i))))
      (vector-set! digits 62 #\-)
      (vector-set! digits 63 #\+)
      (define (number->base-64-string prefix n)
	(let loop ([n n][s null])
	  (if (zero? n)
	      (list->string (cons prefix s))
	      (loop (arithmetic-shift n -6)
		    (cons (vector-ref digits (bitwise-and 63 n)) s)))))
      (define serial-number 0)
      (define (serialize s)
	(cond
	 [(serialize-tex-picts)
	  (set! serial-number (add1 serial-number))
	  (format "~a.~a" serial-number s)]
	 [(tex-series-prefix)
	  (format "~a.~a" (tex-series-prefix) s)]
	 [else s]))
      (define (make-label s)
	(string->symbol
	 (serialize
	  (number->base-64-string
	   #\T
	   (let loop ([l (string->list s)][n 0])
	     (if (null? l)
		 n
		 (loop (cdr l) (+ (arithmetic-shift n 7) (char->integer (car l))))))))))

      (define tex
	(case-lambda
	 [(t) (tex t 10 10)]
	 [(t guess-width guess-height)
	  (let* ([label (make-label t)]
		 [info (or (assq label label-sizes)
			   (let ([v ((current-tex-sizer) t)])
			     (and v
				  (cons label v))))]
		 [w (if info (cadr info) guess-width)]
		 [h (if info (caddr info) guess-height)]
		 [d (if info (cadddr info) guess-height)])
	    (make-pict `(picture ,w ,(+ d h)
				 (put 0 ,d
				      ,(if (output-measure-commands)
					   (format "\\mztpMeasure{~a}{~a}"
						   t label)
					   t)))
		       w
		       (+ d h)
		       h d
		       null
		       #f))]))

      (define (text-line/phantom text phantom . args)
	(apply tex (format "\\makebox[0pt]{\\vphantom{~a}}~a" phantom text) args))

      (define (text-line text . args)
	(apply text-line/phantom text "Xy" args))

      (define (tex-no-descent . args)
	(clip-descent (apply tex args)))

      (define tex-paragraph
	(case-lambda
	 [(w str) (tex-paragraph w str 'top)]
	 [(w str align)
	  (tex (format "\\parbox[~a]{~apt}{~a}"
		       (case align
			 [(top) 't]
			 [(bottom) 'b]
			 [else (error 'tex-paragraph "bad alignment: ~a" align)])
		       w
		       str))]))

      (define delimit-str
	"\\hbox{$\\~a{\\hbox{$\\left~a\\rule{0pt}{~apt}\\right.$}}$}")

      (define (mk-delimit left? middle? right? delim h)
	(let ([str (format delimit-str
			   (cond
			    [left? "mathopen"]
			    [right? "mathclose"]
			    [middle? "mathrel"])
			   delim
			   h)])
	  (tex str 10 h)))

      (define (left-delimit delim h)
	(mk-delimit #t #f #f delim h))
      (define (middle-delimit delim h)
	(mk-delimit #f #t #f delim h))
      (define (right-delimit delim h)
	(mk-delimit #f #f #t delim h))

      (define (left-brace h)
	(left-delimit "\\{" h))
      (define (right-brace h)
	(right-delimit "\\}" h))

      (define (make-h-brace kind w)
	(tex (format "$\\~a{\\hbox{\\begin{picture}(~a,0)(0,0)\\end{picture}}}$"
		     kind w)))

      (define (top-brace w)
	(make-h-brace "overbrace" w))
      (define (bottom-brace w)
	(make-h-brace "underbrace" w))

      (define (generate-possible-slopes n)
	(let ([positives
	       (quicksort
		(remove-duplicates
		 (apply append (build-list n
					   (lambda (i)
					     (build-list n
							 (lambda (j) (/ (+ i 1) (+ j 1))))))))
		<=)])
	  (append
	   (reverse (map - positives))
	   (list 0)
	   positives)))

      (define (remove-duplicates lst)
	(cond
	 [(null? lst) null]
	 [else (cons (car lst)
		     (remove-duplicates
		      (filter
		       (lambda (x) (not (equal? x (car lst))))
		       (cdr lst))))]))

      (define possible-arrow-slopes (generate-possible-slopes 4))
      (define possible-line-slopes (generate-possible-slopes 6))

      (define (find-slope/robby dh dv possible-slopes)
	(if (= dh 0)
	    'vertical
	    (let* ([slope (/ dv dh)])
	      (let loop ([best (car possible-slopes)]
			 [rst (cdr possible-slopes)])
		(cond
		 [(null? rst) best]
		 [else
		  (if (<= (abs (- slope (car rst)))
			  (abs (- slope best)))
		      (loop (car rst) (cdr rst))
		      (loop best (cdr rst)))])))))

      (define (find-slope dh dv max-slope-num h-within v-within) ; max-slope-num is 4 or 6
	; Result is (slope new-dh), where slope can be 'vertical, in which case
	;                           new-dh is really dv
	(letrec ([best-of-two
		  (lambda (a b)
		    (let*-values ([(ls lh) (a)]
				  [(rs rh) (b)])
		      (if (and ls (or (not rs) (< (abs (- lh dh)) (abs (- rh dh)))))
			  (values ls lh)
			  (values rs rh))))]
		 [search-h
		  (lambda (dh dv depth direction)
		    (if (zero? depth)
			(values #f #f)
			(if (zero? dh)
			    (values 'vertical dv)
			    (let ([slope (/ dv dh)])
			      (if (and (<= (abs (numerator slope)) max-slope-num)
				       (<= (abs (denominator slope)) max-slope-num))
				  (values slope dh)
				  (search-h (+ dh direction) dv (sub1 depth) direction))))))]
		 [sign (lambda (x) (if (positive? x) 1 -1))]
		 [flip
		  (lambda (s l)
		    (if s
			(cond
			 [(eq? s 'vertical) (values (sign l) 0 (abs l))]
			 [(zero? s) (values 'vertical l)]
			 [else (values (/ 1 s) (round (* s l)))])
			(values #f #f)))]
		 [search-v
		  (lambda (dh dv depth direction)
		    (call-with-values (lambda () (search-h dv dh depth direction))
				      flip))]
		 [change-h
		  (lambda (dh dv h-within)
		    (best-of-two (lambda () (search-h dh dv h-within -1))
				 (lambda () (search-h dh dv h-within 1))))]
		 [change-v
		  (lambda (dh dv v-within)
		    (call-with-values (lambda () (change-h dv dh v-within))
				      flip))])
	  (cond
	   [(zero? v-within) (change-h dh dv h-within)]
	   [(zero? h-within) (change-v dh dv v-within)]
	   [else (let-values ([(s l) (search-h dh dv 1 0)])
		   (if s
		       (values s l)
		       (best-of-two 	 
			(lambda ()
			  (best-of-two (lambda () (find-slope dh (add1 dv) max-slope-num h-within (sub1 v-within)))
				       (lambda () (find-slope dh (sub1 dv) max-slope-num h-within (sub1 v-within)))))
			(lambda ()
			  (best-of-two (lambda () (find-slope (add1 dh) dv max-slope-num (sub1 h-within) v-within))
				       (lambda () (find-slope (sub1 dh) dv max-slope-num (sub1 h-within) v-within)))))))])))
      
      (define (parse-slope sl dh dv)
	(if (eq? sl 'vertical)
	    (if (negative? dv)
		(values 0 -1 (- dv))
		(values 0 1 dv))
	    (let ([d (denominator sl)]
		  [n (numerator sl)])
	      (if (negative? dh)
		  (values (- d) (- n) (abs dh))
		  (values d n dh)))))

      (define connect 
	(case-lambda
	 [(x1 y1 x2 y2) (connect x1 y1 x2 y2 #f)]
	 [(x1 y1 x2 y2 arrow?)
	  (if (not (or (use-old-connect) (draw-bezier-lines)))
	      (~connect 'r +inf.0 x1 y1 x2 y2 arrow?)
	      (if (draw-bezier-lines)
		  (let* ([get-len (lambda () (sqrt (+ (* (- x1 x2) (- x1 x2))
						      (* (- y1 y2)  (- y1 y2)))))]
			 [c (if (procedure? (draw-bezier-lines))
				((draw-bezier-lines) (get-len))
				#f)])
		    `((qbezier ,c ,x1 ,y1 ,(quotient (+ x1 x2) 2) ,(quotient (+ y1 y2) 2) ,x2 ,y2)))	      
		  (let* ([dh (- x2 x1)]
			 [dv (- y2 y1)]
			 [s
			  (if (using-pict2e-package)
			      (/ dv dh)
			      (find-slope/robby
			       (- x2 x1)
			       (- y2 y1)
			       (if arrow? possible-arrow-slopes possible-line-slopes)))])
		    (let-values ([(lh lv ll) (parse-slope s dh dv)])
		      `((put ,x1 ,y1 (,(if arrow? 'vector 'line) ,lh ,lv ,ll)))))))]))

      #|

      ;; old body of connect

      (let loop ([dd (if (draw-bezier-lines) 0 1)])
	(if (> dd (if (draw-bezier-lines) 0 4))
	    ; give up
	    (if (draw-bezier-lines)
		(let* ([get-len (lambda () (sqrt (+ (* (- x1 x2) (- x1 x2))
						    (* (- y1 y2)  (- y1 y2)))))]
		       [c (if (procedure? (draw-bezier-lines))
			      ((draw-bezier-lines) (get-len))
			      #f)])
		  `((qbezier ,c ,x1 ,y1 ,(quotient (+ x1 x2) 2) ,(quotient (+ y1 y2) 2) ,x2 ,y2)))
		(let ([xd (- x2 x1)])
		  `((put ,x1 ,y1 (line ,(if (negative? xd) -1 1) 0 ,(abs xd))))))
	    (let-values ([(s l) (find-slope (- x2 x1) (- y2 y1) 
					    (if (using-pict2e-package)
						+inf.0
						(if arrow? 4 6))
					    dd dd)])
	      (if s
		  (let-values ([(lh lv ll) (parse-slope s l)])
		    `((put ,x1 ,y1 (,(if arrow? 'vector 'line) ,lh ,lv ,ll))))
		  (loop (add1 dd))))))

      |#

      (define ~connect 
	(case-lambda
	 [(exact close-enough x1 y1 x2 y2) (~connect exact close-enough x1 y1 x2 y2 #f)]
	 [(exact close-enough x1 y1 x2 y2 arrow?)
	  (if (= x2 x1)
	      ; "infinite" slope
	      (let ([dy (- y2 y1)])
		`((put ,x1 ,y1 (,(if arrow? 'vector 'line) 0 ,(if (negative? dy) -1 1) ,(abs dy)))))
	      (let ([real-slope (/ (- y2 y1) (- x2 x1))]
		    [split (lambda (xm ym)
			     (append
			      (~connect exact close-enough xm ym x1 y1 #f)
			      (~connect exact close-enough xm ym x2 y2 arrow?)))])
		(if (or (>= real-slope (if arrow? 7/8 11/12))
			(<= real-slope (if arrow? -7/8 -11/12)))
		    ; rounds to "infinite" slope
		    (if (> (abs (- x2 x1)) close-enough)
			(split x1 (truncate (quotient (+ y1 y2) 2)))
			(let ([dy (- y2 y1)])
			  `((put ,x1 ,y1 (,(if arrow? 'vector 'line) 
					  0 
					  ,(if (negative? dy) -1 1) ,(abs dy))))))
		    (let* ([slope (let loop ([slope real-slope][tolerances
								(if arrow?
								    '(1/100 1/12 1/4)
								    '(1/100 1/50 1/25 1/10 1/6))])
				    (if (<= (denominator slope) (if arrow? 4 6))
					slope
					(loop (rationalize real-slope (car tolerances))
					      (cdr tolerances))))]
			   [exact-x? (or (eq? exact 'x) (zero? slope))]
			   [r (sqrt (+ (* (- x1 x2) (- x1 x2)) (* (- y1 y2) (- y1 y2))))]
			   [dx (cond
				[exact-x? (- x2 x1)]
				[(eq? exact 'r) (truncate (* r (let ([d (denominator slope)]
								     [n (numerator slope)])
								 (/ d (sqrt (+ (* d d) (* n n)))))))]
				[else (truncate (* (/ slope) (- y2 y1)))])]
			   [dy (truncate (* slope dx))])
		      (if (or (and exact-x?
				   (> (abs (- dy (- y2 y1))) close-enough))
			      (and (not exact-x?) (eq? exact 'y)
				   (> (abs (- dx (- x2 x1))) close-enough))
			      (and (not exact-x?) (eq? exact 'y)
				   (> (abs (- (sqrt (+ (* dx dx) (* dy dy))) r)) close-enough)))
			  (if (or exact-x? (eq? exact 'r))
			      (let ([xm (truncate (quotient (+ x1 x2) 2))]) 
				(split xm (+ y1 (truncate (* slope (- xm x1))))))
			      (let ([ym (truncate (quotient (+ y1 y2) 2))]) 
				(split (+ x1 (truncate (* (/ slope) (- ym y1)))) ym)))
			  (let ([same-sign (lambda (v s)
					     (if (negative? s)
						 (- (abs v))
						 (abs v)))])
			    `((put ,x1 ,y1 (,(if arrow? 'vector 'line) 
					    ,(same-sign (denominator slope) (- x2 x1))
					    ,(same-sign (numerator slope) (- y2 y1))
					    ,(abs dx))))))))))]))

      (define (pict->string s)
	(let output ([s (prepare-for-output s)])
	  (if (string? s)
	      s
	      (let ([tag (car s)])
		(case tag
		  [(local)
		   (format "{~a}~n" (output (cadr s)))]
		  [(begin)
		   (apply string-append (map output (cdr s)))]
		  [(picture)
		   (format "\\begin{picture}(~a,~a)~n~a\\end{picture}~n"
			   (cadr s) (caddr s)
			   (apply string-append (map output (cdddr s))))]
		  [(color)
		   (format "\\special{color push ~a}~n~a\\special{color pop}~n"
			   (cadr s) (output (cddr s)))]
		  [(thickness)
		   (format "\\~a~a" (cadr s) (output (caddr s)))]
		  [(put)
		   (format "\\put(~a,~a){~a}~n" (cadr s) (caddr s) (output (cadddr s)))]
		  [(qbezier)
		   (apply format "\\qbezier~a(~a,~a)(~a,~a)(~a,~a)~n"
			  (if (cadr s)
			      (format "[~a]" (cadr s))
			      "")
			  (cddr s))]
		  [(line vector)
		   (format "\\~a(~a,~a){~a}" tag (cadr s) (caddr s) (cadddr s))]
		  [(circle)
		   (format "\\circle{~a}" (cadr s))]
		  [(circle*)
		   (format "\\circle*{~a}" (cadr s))]
		  [(frame)
		   (format "\\frame{~a}" (output (cadr s)))]
		  [(colorbox)
		   (format "\\colorbox{~a}{~a}" (cadr s) (output (caddr s)))]
		  [(oval)
		   (format "\\oval(~a,~a)~a" (caddr s) (cadddr s) (cadr s))]
		  [(make-box)
		   (format "\\makebox(~a, ~a)[~a]{~a}"
			   (cadr s) (caddr s) (cadddr s) (car (cddddr s)))]
		  [(prog)
		   (error 'pict->string "cannot handle prog pict")]
		  [else (error 'pict->string "bad tag: ~s" tag)])))))

      (define pict->commands pict->command-list))))

