(in-package :clog-tools)

;; Code rendering utlities

(defun pprint-as-html (content)
  (js-query content (format nil "html_beautify ('~A',{'wrap_line_length':'80',~
     'extra_liners':'div,form,input,button,select,textarea,ol,ul,table,style,datalist'})"
                            (escape-string (remove-clog-attributes (inner-html content))))))

(defun remove-clog-attributes (html)
  (let ((root (plump:parse html)))
    (html-remove-attributes root '("data-clog-type"
                                   "data-original-html"
                                   "data-clog-composite-control"
                                   "data-clog-custom-create"))
    (with-output-to-string (str)
      (plump:serialize root str))))

(defun html-remove-attributes (root attrs)
  (let ((v (plump:child-elements root)))
    (dotimes (n (length v))
      (if (equalp (plump:tag-name (aref v n)) "data")
          (plump:remove-child (aref v n))
          (progn
            (when (plump:child-elements (aref v n))
              (let ((composite (plump:attribute (aref v n) "data-clog-composite-control")))
                (if composite
                    (progn
                      (if (equalp composite "b")
                          (let* ((new-html     (plump:parse (plump:attribute (aref v n) "data-original-html")))
                                 (new-children (plump:child-elements new-html)))
                            (plump:clear (aref v n))
                            (dotimes (cn (length new-children))
                              (plump:append-child (aref v n) (aref new-children cn))))
                          (plump:clear (aref v n))))
                    (html-remove-attributes (aref v n) attrs))))
            (maphash (lambda (k v)
                       (declare (ignore v))
                       (unless (ppcre:scan "^data-clog-name" k)
                         (when (ppcre:scan "^data-clog-" k)
                           (pushnew k attrs :test 'equalp)))
                       (when (ppcre:scan "^data-on-" k)
                         (pushnew k attrs :test 'equalp)))
                     (plump:attributes (aref v n)))
            (mapcar (lambda (attr)
                      (plump:remove-attribute (aref v n) attr))
                    attrs))))))

(defun render-clog-code (content hide-loc)
  "Render panel to clog code and add to window"
  (let* ((app      (connection-data-item content "builder-app-data"))
         (panel-id (html-id content))
         (package  (attribute content "data-in-package"))
         (slots    (attribute content "data-custom-slots"))
         (cname    (attribute content "data-clog-name"))
         cmembers vars creates events)
    (unless (or (equal slots "")
                (equal slots "undefined"))
      (push slots cmembers))
    (maphash (lambda (html-id control)
               (declare (ignore html-id))
               (place-inside-bottom-of hide-loc
                                       (get-placer control)))
             (get-control-list app panel-id))
    ;; crawl tree
    ;; Insure that on-setup/on-create follow order in tree
    (labels ((add-siblings (control)
               (let (dct)
                 (loop
                   (unless control (return))
                   (when (equal (html-id control) "undefined") (return))
                   (setf dct (attribute control "data-clog-name"))
                   (unless (equal dct "undefined")
                     (setf control (get-from-control-list app panel-id (html-id control)))
                     (when control
                       (let* ((vname (attribute control "data-clog-name"))
                              (control-record (control-info (attribute control "data-clog-type"))))
                         (unless (and (>= (length vname) 5)
                                      (equalp (subseq vname 0 5) "none-"))
                           ;; Add to members of the panel's class for each control
                           (push (format nil
                                         "    \(~A :reader ~A\)~%"
                                         vname
                                         vname)
                                 cmembers)
                           ;; On instance of class, set member value for each control
                           (push (format nil
                                         "    \(setf (slot-value panel '~A\) ~
                                            \(attach-as-child clog-obj \"~A\" :clog-type \'~A\ :new-id t)\)~%"
                                         vname
                                         (html-id control)
                                         (if (eq (getf control-record :create-type) :custom-panel)
                                             (format nil "~A" (attribute control "data-clog-custom-type"))
                                             (format nil "~S" (getf control-record :clog-type))))
                                 vars)
                           ;; On instance of class, set handers defined for each control
                           (dolist (event (getf control-record :events))
                             ;; Set regular handlers
                             (let ((handler (attribute control (format nil "data-~A" (getf event :name)))))
                               (unless (or (equalp handler "undefined")
                                           (equal handler ""))
                                 (unless (equalp (getf event :name) "on-create")
                                   (let ((event-package (or (getf event :package) "clog")))
                                     (push (format nil
                                                   "    \(~A:set-~A \(~A panel\) \(lambda \(~A\) \(declare \(ignorable ~A\)\) ~A\)\)~%"
                                                   event-package
                                                   (getf event :name)
                                                   vname
                                                   (getf event :parameters)
                                                   (getf event :parameters)
                                                   handler)
                                           events))))))
                           ;; Set on-create (from user in builder) and on-setup (from control-record)
                           (let ((handler (attribute control "data-on-create")))
                             (when (equalp handler "undefined")
                               (setf handler ""))
                             (when (getf control-record :on-setup)
                               (setf handler (format nil "~A~A"
                                                     (funcall (getf control-record :on-setup)
                                                              control control-record)
                                                     handler)))
                             (unless (equal handler "")
                               (push (format nil
                                             "    \(let \(\(target \(~A panel\)\)\) ~
                                             \(declare \(ignorable target\)\) ~
                                              ~A\)~%"
                                             vname
                                             handler)
                                     creates)))))
                       (add-siblings (first-child control))))
                   (when control
                     (setf control (next-sibling control)))))))
      (add-siblings (first-child content)))
    (let ((result (format nil
                          "\(in-package :~A)
\(defclass ~A \(clog:clog-panel\)
  \(~{~A~}\)\)
\(defun create-~A \(clog-obj &key hidden class style html-id \(auto-place t\)\)
  \(let \(\(panel \(change-class \(clog:create-div clog-obj :content \"~A\"
         :hidden hidden :class class :style style :html-id html-id :auto-place auto-place\) \'~A\)\)\)
~{~A~}~{~A~}~{~A~}    panel\)\)~%"
                          (string-downcase package)
                          cname     ;;defclass
                          cmembers
                          cname     ;;defun
                          (ppcre:regex-replace-all "\""
                                                   (pprint-as-html content)
                                                   "\\\"")
                          cname
                          vars
                          (reverse creates)   ; Insure that on-setup/on-create follow order in tree
                          (reverse events))))
      (maphash (lambda (html-id control)
                 (declare (ignore html-id))
                 (place-after control (get-placer control)))
               (get-control-list app panel-id))
      ;; prety print the code
      (let ((r (make-array '(0) :element-type 'base-char
                                :fill-pointer 0 :adjustable t)))
        (with-output-to-string (s r)
          (with-input-from-string (n result)
            (let ((*standard-output*    s)
                  (*print-right-margin* *builder-render-right-margin* )
                  (*print-case*         *builder-render-case*))
              (format t ";;;; CLOG Builder generated code - modify original .clog file and rerender")
              (loop
                (let ((l (read n nil)))
                  (unless l (return))
                  (pprint l))))))
        r))))
