#lang racket/base
(require racket/class
         racket/gui/base
         racket/match
         (only-in mzlib/string read-from-string)
         framework
         "display.rkt"
         "controller.rkt"
         "keymap.rkt"
         "util.rkt"
         "prefs.rkt")

(provide syntax-snip%
         marshall-syntax
         unmarshall-syntax
         snip-class)

;; syntax-snip%
(define syntax-snip%
  (class* editor-snip% (readable-snip<%>)
    (init-field ((stx syntax)))
    (init-field (controller (new controller%)))
    (init-field (config (new syntax-prefs/readonly%)))
    (init-field (columns 40))

    (inherit set-margin
             set-inset
             set-snipclass)

    (define text (new text:standard-style-list%))
    (super-new (editor text) (with-border? #f))

    (set-margin 0 0 0 0)
    ;;(set-inset 2 2 2 2)
    ;;(set-margin 2 2 2 2)
    (set-inset 0 0 0 0)

    (define display
      (with-unlock text
        (send text change-style (make-object style-delta% 'change-alignment 'top))
        (print-syntax-to-editor stx text controller config columns)))
    (send text hide-caret #t)

    (setup-keymap text)

    (define/public (setup-keymap text)
      (new syntax-keymap%
           (controller controller)
           (config config)
           (editor text)))

    ;; snip% Methods
    (define/override (copy)
      (new syntax-snip%
           (config config)
           (controller controller)
           (syntax stx)))

    ;; read-special : any number/#f number/#f number/#f -> syntax
    ;; Produces 3D syntax to preserve eq-ness of syntax
    ;; #'#'stx would be lose identity when wrapped
    (define/public (read-special src line col pos)
      (with-syntax ([p (lambda () stx)])
        #'(p)))
    
    (define/override (write stream)
      (send stream put
            (string->bytes/utf-8
             (format "~s" (marshall-syntax stx)))))

    (set-snipclass snip-class)))

;; Marshalling stuff

;; marshall-syntax : syntax -> printable
(define (marshall-syntax stx)
  (unless (syntax? stx)
    (error 'marshall-syntax "not syntax: ~s\n" stx))
  `(syntax
    (source ,(marshall-object (syntax-source stx)))
    (source-module ,(marshall-object (syntax-source-module stx)))
    (position ,(syntax-position stx))
    (line ,(syntax-line stx))
    (column ,(syntax-column stx))
    (span ,(syntax-span stx))
    (original? ,(syntax-original? stx))
    (properties 
     ,@(map (λ (x) `(,x ,(marshall-object (syntax-property stx x))))
            (syntax-property-symbol-keys stx)))
    (contents
     ,(marshall-object (syntax-e stx)))))

;; marshall-object : any -> printable
;; really only intended for use with marshall-syntax
(define (marshall-object obj)
  (cond
    [(syntax? obj) (marshall-syntax obj)]
    [(pair? obj) 
     `(pair ,(cons (marshall-object (car obj))
                   (marshall-object (cdr obj))))]
    [(or (symbol? obj)
         (char? obj)
         (number? obj)
         (string? obj)
         (boolean? obj)
         (null? obj))
     `(other ,obj)]
    [else (string->symbol (format "unknown-object: ~s" obj))]))

(define (unmarshall-syntax stx)
  (match stx
    [`(syntax
       (source ,src)
       (source-module ,source-module) ;; marshalling
       (position ,pos)
       (line ,line)
       (column ,col)
       (span ,span)
       (original? ,original?)
       (properties . ,properties)
       (contents ,contents))
      (foldl
       add-properties
       (datum->syntax
        #'here ;; ack
        (unmarshall-object contents)
        (list (unmarshall-object src)
              line
              col
              pos
              span))
       properties)]
    [else #'unknown-syntax-object]))

;; add-properties : syntax any -> syntax
(define (add-properties prop-spec stx)
  (match prop-spec
    [`(,(and sym (? symbol?))
        ,prop)
      (syntax-property stx sym (unmarshall-object prop))]
    [else stx]))

(define (unmarshall-object obj)
  (let ([unknown (lambda () (string->symbol (format "unknown: ~s" obj)))])
    (if (and (pair? obj)
             (symbol? (car obj)))
        (case (car obj)
          [(pair) 
           (if (pair? (cdr obj))
               (let ([raw-obj (cadr obj)])
                 (if (pair? raw-obj)
                     (cons (unmarshall-object (car raw-obj))
                           (unmarshall-object (cdr raw-obj)))
                     (unknown)))
               (unknown))]
          [(other) 
           (if (pair? (cdr obj))
               (cadr obj)
               (unknown))]
          [(syntax) (unmarshall-syntax obj)]
          [else (unknown)])
        (unknown))))

;; SNIPCLASS

;; COPIED AND MODIFIED from mrlib/syntax-browser.rkt
(define syntax-snipclass%
  (class snip-class%
    (define/override (read stream)
      (make-object syntax-snip%
        (unmarshall-syntax (read-from-string (send stream get-bytes)))))
    (super-instantiate ())))

(define snip-class (new syntax-snipclass%))
(send snip-class set-version 2)
(send snip-class set-classname
      (format "~s" '(lib "macro-debugger/syntax-browser/snip.rkt")))
