 (defn line->contentline [s]
   (let [m (s/conform ::contentline (seq s))]
     (when-not (:name m) (throw (ex-info "No name" {:contentline s})))
-    {:name (-> m :name extract-name-as-string)
-     :params (->> m :params
-                  (map (juxt
-                         (comp extract-name-as-string :param-name :param)
-                         (comp extract-param-value-as-string :param-value :param)))
-                  (into {} ))
-     :value (-> m :value str/join)}))
+    (let [str-value (-> m :value str/join)]
+      {:name (-> m :name extract-name-as-string)
+       :params (->> m :params
+                    (map (juxt
+                           (comp extract-name-as-string :param-name :param)
+                           (comp extract-param-value-as-string :param-value :param)))
+                    (into {} ))
+       ;; We set to the string, but properties may replace this with
+       ;; another type
+       :value str-value
+       ;; We always retain the original string value
+       :string-value str-value})))
 
 ;; JCF tip
 ;;(s/conform (s/and (s/conformer seq) ::iana-token) "foobar")

  Instant
  (serialize-value [i] {:value (.format (ZonedDateTime/ofInstant i (ZoneId/of "UTC")) DATE-TIME-FORM-2-PATTERN)})
  LocalDate
  (serialize-value [s] {:value (.format s DATE-PATTERN)})
  LocalDateTime
  (serialize-value [s] {:value (.format s DATE-TIME-FORM-1-PATTERN)})
  ZonedDateTime


(defmethod add-contentline-to-model "END"
  [acc contentline]
  ;; END means place the current object in the stack, and start a new
  ;; one
  (let [curr-object (instantiate (:curr-object acc))
        restore-object (some-> (:stack acc) last) ; check if nil, bad state
        restore-object (update restore-object :subobjects (fnil conj []) curr-object)]
    (-> acc
        (assoc :curr-object restore-object
               :stack (vec (butlast (:stack acc)))))))

(defmulti coerce-to-value (fn [value-type value] value-type))
(defmethod coerce-to-value "DATE" [_ value] (LocalDate/parse value DateTimeFormatter/BASIC_ISO_DATE))
(defmethod coerce-to-value nil [_ value] value)

(defn add-property [acc contentline]
  (update-in
    acc [:curr-object :properties] (fnil conj [])
    (update contentline
            :value
            #(coerce-to-value (get-in contentline [:params "VALUE"]) %))))

(defmethod add-contentline-to-model :default
  [acc contentline]
  (add-property acc contentline))

(defn parse-icalendar [^java.io.BufferedReader r]
  (->> r
       unfolding-line-seq
       (map line->contentline)
       (map-indexed (fn [n o] (assoc o :lineno (inc n))))
       (reduce add-contentline-to-model {})
       :curr-object
       :subobjects))

(defn property [obj prop-name]
  (filter #(= (:name %) (str/upper-case (name prop-name))) (:properties obj)))

;; Now individual calendar sources

(defn bank-holidays-in-england-and-wales [] nil)

(defn events
  "Given a vcalendar object, return only the events"
  [vcalendar]
  (filter #(= "VEVENT" (:object %)) (:subobjects vcalendar)))

#_(parse-icalendar (io/reader (io/file "test.ics")))

#_(parse-icalendar (io/reader (io/resource "ics/gov.uk/england-and-wales.ics")))

#_(events (first (parse-icalendar (io/reader (io/resource "ics/gov.uk/england-and-wales.ics")))))
