#lang racket/base
(require racket/system racket/port racket/file 
           racket/class racket/list)
(require "debug.rkt" "readability.rkt" "world.rkt")

(provide setup)

(define cd (current-directory))
(define test-mode (make-parameter #f))

;; simple class for setup tasks
(define task% 
  (class object%
    (init do-proc [undo-proc #f])
    (define this-do do-proc)
    (define this-undo undo-proc)
    (super-new)
    (define/public (do-task) (this-do))
    (define/public (undo-task) (if this-undo (this-undo) (void)))))

;; function for invoking method of multiple objects
(define-syntax send-each
  (syntax-rules ()
    ;; invoke a method of every object in list
    [(send-each objects do-method)
     (for-each (λ(obj) (send obj do-method)) objects)]
    ;; invoke a method of every object in list, but
    ;; if error, invoke the undo method all the way backwards
    ;; allows structured cleanup
    [(send-each objects do-method undo-method)
     (begin
       (define (send-stack-inner objects-inner)
         (if (empty? objects-inner)
             (void)
             (with-handlers ([exn:fail? (λ(err) 
                                          (begin 
                                            (send (car objects-inner) undo-method) 
                                            (raise err)))])
               (send (car objects-inner) do-method)
               (send-stack-inner (cdr objects-inner)))))
       ;; error has been handled all the way back up the stack
       ;; so kill it here 
       (with-handlers ([exn:fail? (λ(err) (void))])
         (send-stack-inner objects)))]))



(define (make-polcom-data racket-path)
  (format "#! ~a
#lang racket/base
(require pollen/command)
;; pollen setup run in ~a on ~a" 
          racket-path
          cd
          (format "~a at ~a" (make-datestamp) (make-timestamp))))


;; task: get confirmation
(define (get-confirmation)
  (message (format "Set up ~a as a pollen directory? ['y' or 'yes']" cd))
  (define setup-confirm (->string (read)))
  (when (not (ormap (λ(input)(equal? setup-confirm input)) (list "y" "yes")))
    (error))
  (message (format "Setting up ~a as a pollen directory" cd)))

(define (abort-confirmation)
  (message "Aborting setup"))

(define confirm-setup
  (new task% 
       [do-proc get-confirmation]
       [undo-proc abort-confirmation]))


;; task: make polcom file

(define (delete-polcom-file-if-existing)
  (when (file-exists? POLLEN_COMMAND_FILE)
    (begin
      (message (format "Deleting existing polcom file in ~a" cd))
      (delete-file POLLEN_COMMAND_FILE))))

(define (save-polcom-file)
  (define path-to-racket-exists? (> (len RACKET_PATH) 0)) 
  
  (if path-to-racket-exists?
      (let ([polcom-data (make-polcom-data RACKET_PATH)])
        (message (format "Using ~a as racket path" RACKET_PATH))
        (delete-polcom-file-if-existing)
        (message (format "Creating new polcom file in ~a" cd))
        (if (not (test-mode))
            (begin
              (display-to-file polcom-data POLLEN_COMMAND_FILE)
              (with-output-to-string (λ() (system (format "chmod 755 ~a" POLLEN_COMMAND_FILE)))))
            (message "[test mode: file would be saved now]")))
      (begin 
        (message "No path to Racket binary")
        (error))))

(define (abort-polcom-file)
  (message "Couldn't create polcom file")
  (delete-polcom-file-if-existing))

(define make-polcom
  (new task%
       [do-proc save-polcom-file]
       [undo-proc abort-polcom-file]))


;; task: report success

(define (success-messages)
  (message "Setup complete")
  (define path-to-polcom (format "~a~a" cd POLLEN_COMMAND_FILE))
  (message (format "Run '~a start' to start project server" path-to-polcom))
  (message (format "Or run '~a help' for a list of commands" path-to-polcom))
  (when (not (test-mode)) (exit)))

(define report-success
  (new task% 
       [do-proc success-messages]))

(define (setup)
  (define tasks (list confirm-setup make-polcom report-success))
  (send-each tasks do-task undo-task))


;; better way to do this is to create separate removal tasks
;; (that may rely on some of the same intermediate functions)
;; so that errors can be caught on the way out too.
;; also, remove conflicts with existing racket name
;;(define (remove)
;;  (send-each (reverse tasks) remove-task))

(module+ main
  (parameterize ([test-mode #t])
    (setup)))