#!/usr/bin/env sbcl --script
(load "~/.sbclrc")
(require :cl-ppcre)
(require :sqlite)
(require :local-time)

(defvar *db* (sqlite:connect "/Users/bpanthi977/org/dbs/darwin/org-roam.db"))

(defparameter *created-date-regex* (cl-ppcre:create-scanner
			    '(:sequence
			      "#+date:"
			      (:greedy-repetition 0 nil (:alternation
							 :whitespace-char-class
							 #\[ #\<))
			      (:register (:greedy-repetition 0 nil
					  (:alternation :digit-class #\-))))
			    :case-insensitive-mode t))

(defparameter *edited-date-regex* (cl-ppcre:create-scanner
			    '(:sequence
			      "#+edited:"
			      (:greedy-repetition 0 nil (:alternation
							 :whitespace-char-class
							 #\[ #\<))
			      (:register (:greedy-repetition 0 nil
					  (:alternation :digit-class #\-))))
			    :case-insensitive-mode t))

(defun get-dates (org-file)
  (let* ((str (uiop:read-file-string org-file)))
    (flet ((get-match (regex)
	     (cl-ppcre:register-groups-bind (match)
		 (regex str)
	       match)))
      (list (get-match *created-date-regex*)
	    (get-match *edited-date-regex*)))))

(defun unquote (str)
  (subseq str 1 (1- (length str))))

(defun html-org-file (html-file)
  (make-pathname
   :name (pathname-name html-file)
   :type "org"
   :directory (pathname-directory (truename #p"~/org/"))))

(defun get-file-title (org-file)
  (unquote (sqlite:execute-single *db* "
SELECT title FROM files
WHERE file = ?
" (format nil "~s" (namestring org-file)))))

(defun format-rss-date (date)
  (local-time:format-timestring
   nil
   (if (typep date 'local-time:timestamp)
       date
       (local-time:parse-timestring date))
   :format '(:weekday ", " (:day 2) " " :month " " (:year 4) " "
	     (:hour 2) ":" (:min 2) ":" (:sec 2) " " :timezone)))

(defun write-string-with-indent (string stream &key (indent 0) (start 0) (end (length string)))
  (unless (<= end start)
    (loop repeat indent do (write-char #\Space stream)))

  (loop for i from start below end
	for char = (char string i) do
	  (write-char char stream)
	  (when (char-equal char #\Newline)
	    (unless (or (eql i (1- end))
			(char-equal (char string (1+ i)) #\Newline))
	      (loop repeat indent do (write-char #\Space stream))))))



(defun generate-entry (stream html-file id)
  (declare (optimize (debug 3) (speed 0)))
  (let* ((org-file (html-org-file html-file))
	 (title (get-file-title org-file))
	 (url (format nil "https://bpanthi977.com/braindump/~a.html" (pathname-name html-file)))
	 (dates (get-dates org-file)))
    (let* ((content (uiop:read-file-string html-file))
	   (title-end (+ (length "</h1>") (search "</h1>" content)))
	   (backref-start (search "<div id=\"backrefs\"" content :from-end t))
	   (postamble-start (search "<div id=\"postamble\"" content :from-end t))
	   (last-div (+ (length "</div>") (search "</div>" content :from-end t))))
      (format stream "<item>
  <title>~a</title>
  <link>~a</link>
  <author>Bibek Panthi</author>
  <guid isPermaLink=\"false\">~a</guid>
  <pubDate>~a</pubDate>
  <description><![CDATA[<p>
"
	      title
	      url
	      id
	      (format-rss-date (first dates)))
      (write-string-with-indent
       content
       stream
       :indent 4
       :start title-end
       :end (or backref-start postamble-start last-div))
      (format stream "
    ]]></description>
</item>
"))))

(defun generate-rss (ids output-file)
  (with-open-file (stream output-file
			  :direction :output
			  :if-exists :supersede)
    (format stream "<?xml version=\"1.0\" encoding=\"utf-8\"?>
<rss version=\"2.0\"
     xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"
     xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"
     xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
     xmlns:atom=\"http://www.w3.org/2005/Atom\"
     xmlns:sy=\"http://purl.org/rss/1.0/modules/syndication/\"
     xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"
     xmlns:georss=\"http://www.georss.org/georss\"
     xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"
     xmlns:media=\"http://search.yahoo.com/mrss/\"><channel>
  <title>Bibek's Digital Garden</title>
  <atom:link href=\"https://bpanthi977.com/braindump/data/rss.xml\" rel=\"self\" type=\"application/rss+xml\" />
  <link>https://bpanthi977.com/braindump/</link>
  <description><![CDATA[]]></description>
  <language>en</language>
  <pubDate>~a</pubDate>
  <lastBuildDate>~a</lastBuildDate>
  <webMaster>Bibek Panthi</webMaster>
  <image>
    <url>https://bpanthi977.com/braindump/data/rss.png</url>
    <title>Bibek's Digital Garden</title>
    <link>https://bpanthi977.com/braindump/</link>
  </image>
"
	    (format-rss-date (local-time:now))
	    (format-rss-date (local-time:now)))

    (loop for id in ids
	  for file = (make-pathname :name (pathname-name (get-id-file id))
				    :type "html")
	  do
      (generate-entry stream file id))

    (format stream "
</channel>
</rss>
"
		    )))

(defparameter *id-link-regex* (cl-ppcre:create-scanner
			       '(:sequence
				 "[[id:"
				 (:register (:greedy-repetition 0 nil
					     (:alternation :digit-class :word-char-class #\-)))
				 "]")
			    :case-insensitive-mode t))

(defun get-id-file (id)
  (let ((file (sqlite:execute-single *db* "
SELECT file FROM nodes
WHERE id = ?
  AND level = 0
" (format nil "~s" id))))
    (unless file
      (error "Invalid id ~a. ID must be top level id of a file." id))
    (unquote file)))

(defun get-rss-include-file-ids (rss-org)
  (let ((file-ids nil))
    (cl-ppcre:do-register-groups (id) (*id-link-regex* (uiop:read-file-string rss-org))
      (push id file-ids))
    (reverse file-ids)))

(defun main()
  (generate-rss (get-rss-include-file-ids "~/org/rss.org")
		"data/rss.xml"))

(main)
