(require 'icalendar)

;;; add this to org-capture-templates:

;; ("K" "Kill Ring Head" entry
;;  (file+headline "~/.org/life.org" "Dates")
;;  "%c")

(defun icalendar--get-event-properties-full (event prop)
  "For the given EVENT return a list of all occurrences of the property PROP."
  (let ((props (car (cddr event))) pp result)
    (while props
      (setq pp (car props))
      (if (eq (car pp) prop)
          (setq result (cons pp result)))
      (setq props (cdr props)))
    result))

(defun ical-parse-important-data (ical-list)
  "Return important data from an ical-list (which is returned from icalendar--read-element)"
  (let* ((ev (icalendar--all-events ical-list))
	 (error-string "")
	 (event-ok t)
	 (found-error nil)
	 (zone-map (icalendar--convert-all-timezones ical-list))
	 e result)
    ;; step through all events/appointments
    (while ev
      (setq e (car ev))
      (setq ev (cdr ev))
      (setq event-ok nil)
      (condition-case error-val
	  (let* ((dtstart (icalendar--get-event-property e 'DTSTART))
		 (dtstart-zone (icalendar--find-time-zone
				(icalendar--get-event-property-attributes
				 e 'DTSTART)
				zone-map))
		 (dtstart-dec (icalendar--decode-isodatetime dtstart nil
							     dtstart-zone))
		 (start-d dtstart-dec)
		 (start-t dtstart-dec)
		 (dtend (icalendar--get-event-property e 'DTEND))
		 (dtend-zone (icalendar--find-time-zone
			      (icalendar--get-event-property-attributes
			       e 'DTEND)
			      zone-map))
		 (dtend-dec (icalendar--decode-isodatetime dtend
							   nil dtend-zone))
		 (dtend-1-dec (icalendar--decode-isodatetime dtend -1
							     dtend-zone))
		 end-d
		 end-1-d
		 end-t
		 (summary (icalendar--convert-string-for-import
			   (or (icalendar--get-event-property e 'SUMMARY)
			       "No summary")))
		 (rrule (icalendar--get-event-property e 'RRULE))
		 (rdate (icalendar--get-event-property e 'RDATE))
		 (duration (icalendar--get-event-property e 'DURATION)))
	    (icalendar--dmsg "%s: `%s'" start-d summary)
	    ;; check whether start-time is missing
	    (if  (and dtstart
		      (string=
		       (cadr (icalendar--get-event-property-attributes
			      e 'DTSTART))
		       "DATE"))
		(setq start-t nil))
	    (when duration
	      (let ((dtend-dec-d (icalendar--add-decoded-times
				  dtstart-dec
				  (icalendar--decode-isoduration duration)))
		    (dtend-1-dec-d (icalendar--add-decoded-times
				    dtstart-dec
				    (icalendar--decode-isoduration duration
								   t))))
		(if (and dtend-dec (not (eq dtend-dec dtend-dec-d)))
		    (message "Inconsistent endtime and duration for %s"
			     summary))
		(setq dtend-dec dtend-dec-d)
		(setq dtend-1-dec dtend-1-dec-d)))
	    (setq end-d (if dtend-dec
			    dtend-dec
			  start-d))
	    (setq end-1-d (if dtend-1-dec
			      (icalendar--datetime-to-diary-date dtend-1-dec)
			    start-d))
	    (setq end-t (if (and
			     dtend-dec
			     (not (string=
				   (cadr
				    (icalendar--get-event-property-attributes
				     e 'DTEND))
				   "DATE")))
			    dtend-dec
			  start-t))
	    (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
	    (let ((location (icalendar--get-event-property e 'LOCATION))
		  (organizer (icalendar--get-event-properties-full e 'ORGANIZER))
		  (summary (icalendar--get-event-property e 'SUMMARY))
		  (description (icalendar--get-event-property e 'DESCRIPTION))
		  (attendees (icalendar--get-event-properties-full e 'ATTENDEE))
		  (uid (icalendar--get-event-property e 'UID))
		  (sequence (icalendar--get-event-property e 'SEQUENCE))
		  (created (icalendar--decode-isodatetime (icalendar--get-event-property e 'CREATED)))
		  (last-modified (icalendar--decode-isodatetime (icalendar--get-event-property e 'LAST-MODIFIED))))
	      (setq result (cons `(start ,start-d
					   end ,end-d
					   location ,location
					   organizer ,organizer
					   summary ,summary
					   description ,description 
					   attendees ,attendees
					   uid ,uid
					   sequence ,sequence
					   created ,created
					   last-modified ,last-modified) result)))
	    ;; (cond
	    ;;  ;; recurring event
	    ;;  (rrule
	    ;;   (setq diary-string
	    ;; 	  (icalendar--convert-recurring-to-diary e dtstart-dec start-t
	    ;; 						 end-t))
	    ;;   (setq event-ok t))
	    ;;  (rdate
	    ;;   (icalendar--dmsg "rdate event")
	    ;;   (setq diary-string "")
	    ;;   (mapc (lambda (datestring)
	    ;; 	    (setq diary-string
	    ;; 		  (concat diary-string
	    ;; 			  (format "......"))))
	    ;; 	  (icalendar--split-value rdate)))
	    ;;  ;; non-recurring event
	    ;;  ;; all-day event
	    ;;  ((not (string= start-d end-d))
	    ;;   (setq diary-string
	    ;; 	  (icalendar--convert-non-recurring-all-day-to-diary
	    ;; 	   e start-d end-1-d))
	    ;;   (setq event-ok t))
	    ;;  ;; not all-day
	    ;;  ((and start-t (or (not end-t)
	    ;; 		     (not (string= start-t end-t))))
	    ;;   (setq diary-string
	    ;; 	  (icalendar--convert-non-recurring-not-all-day-to-diary
	    ;; 	   e dtstart-dec dtend-dec start-t end-t))
	    ;;   (setq event-ok t))
	    ;;  ;; all-day event
	    ;;  (t
	    ;;   (icalendar--dmsg "all day event")
	    ;;   (setq diary-string (icalendar--datetime-to-diary-date
	    ;; 			dtstart-dec "/"))
	    ;;   (setq event-ok t)))
	    ;; add all other elements unless the user doesn't want to have
	    ;; them
	    ;; (if event-ok
	    ;;     (progn
	    ;; 	;; (setq diary-string
	    ;; 	;;       (concat diary-string " "
	    ;; 	;; 	      (icalendar--format-ical-event e)))
	    ;; 	;; (if do-not-ask (setq summary nil))
	    ;; 	;; ;; add entry to diary and store actual name of diary
	    ;; 	;; ;; file (in case it was nil)
	    ;; 	;; (setq diary-file
	    ;; 	;;       (icalendar--add-diary-entry diary-string diary-file
	    ;; 	;; 				  non-marking summary))
	    ;; 	(format )
	    ;; 	)
	    ;;   ;; event was not ok
	    ;;   (setq found-error t)
	    ;;   (setq error-string
	    ;; 	  (format "%s\nCannot handle this event:%s"
	    ;; 		  error-string e)))
	    )))
    result))


(defun org-ical-format-contact (organizer-list)
  (let ((e-mail (car (last organizer-list)))
	(cn (find 'CN  organizer-list :test (lambda (key item) (and (listp item) (eq (car item) key))))))
    (if (string-prefix-p "mailto:" e-mail t)
	(setq e-mail (substring e-mail (length "mailto:"))))
    (format "%s<%s>" (if cn (format "\"%s\" " (cadr cn)) "") e-mail)))

(defun org-ical-parse-file (filename)
  (save-current-buffer
    (set-buffer (find-file filename))
    (prog1 
	(ical-parse-important-data (icalendar--read-element nil nil))
      (kill-buffer))))

(defun org-ical-create-entry (event)
  (let* ((start (plist-get event 'start))
	 (end (plist-get event 'end))
	 (uid (plist-get event 'uid))
	 (description (plist-get event 'description))
	 (location (plist-get event 'location))
	 (organizer (plist-get event 'organizer))
	 (summary (plist-get event 'summary))
	 (created (plist-get event 'created))
	 (last-modified (plist-get event 'last-modified))
	 (sequence (plist-get event 'sequence))
	 (priority (plist-get event 'priority))
	 (attendees (plist-get event 'attendees)))
    (cons uid (replace-regexp-in-string "\\\\n" "\n"
					(replace-regexp-in-string "\\\\," "," (substring-no-properties (format 
													"* %s
  :PROPERTIES:
    :ID: %s
    :START-TIME: %s
    :END-TIME: %s
    :LOCATION: %s
    :ORGANIZER: %S
    :ATTENDEES: %S
    :CREATED: %s
    :LAST-MODIFIED: %s
    :SEQUENCE: %s
    :PRIORITY: %s
  :END:
  %s
  %s--%s"
													summary
													uid
													(apply 'encode-time start)
													(apply 'encode-time end)
													location
													(org-ical-format-contact (car organizer))
													(mapcar 'org-ical-format-contact attendees)
													(if created (format-time-string (org-time-stamp-format t t) (apply 'encode-time created)) nil) 
													(if last-modified (format-time-string (org-time-stamp-format t t) (apply 'encode-time last-modified)) nil) 
													sequence
													priority
													description
													(format-time-string (org-time-stamp-format t) (apply 'encode-time start))
													(format-time-string (org-time-stamp-format t) (apply 'encode-time end))
)))))))

(defun org-ical-insert-or-update-event (filename)
  (let* ((events (org-ical-parse-file filename))
	 (uid+org-entries (mapcar 'org-ical-create-entry events)))
    (dolist (uid+entry uid+org-entries)
      (let ((uid (car uid+entry))
	    (entry (cdr uid+entry)))
	(let ((org-entry (org-id-find uid)))
	  (if org-entry
	      (org-ical-update-event entry org-entry)
	    (org-ical-insert-event entry)))))))

(defun org-ical-insert-event (new-entry)
  (kill-new (substring-no-properties new-entry))
  (org-capture nil "K"))

(defun org-ical-update-event (new-entry org-entry)
;; use org-narrow-to-subtree or org-tree-to-indirect-buffer
  (error "Not implemented yet, updating event %S with existing entry %S" new-entry org-entry))

(provide 'org-ical)

;(org-ical-insert-or-update-event "/tmp/test.ical")
;(org-ical-parse-file "/tmp/test.ical")



