#lang s-exp "../moby-lang.ss"
(define MYMAPS-URL
(string-append "http://maps.google.com/maps/ms"
"?ie=UTF8&hl=en&msa=0&output=georss&"
"msid=106933521686950086948"
".000473bafba93dfb155a0"))
(define UNINITIALIZED 'uninitialized)
(define-struct world (loc closest last-reported sms enabled?))
(define-struct loc (lat long))
(define-struct place (name loc radius))
(define (place-uninitialized? a-place)
(eq? a-place UNINITIALIZED))
(define (place-unknown? a-place)
(cond [(place-uninitialized? a-place)
false]
[else
(string=? (place-name a-place) "Unknown")]))
(define initial-world (make-world (make-loc 0 0)
UNINITIALIZED
UNINITIALIZED
""
false))
(define (move w lat long)
(make-world (make-loc lat long)
(closest-place (make-loc lat long))
(world-last-reported w)
(world-sms w)
(world-enabled? w)))
(define (record-reporting w)
(cond
[(should-send-report? w)
(make-world (world-loc w)
(world-closest w)
(world-closest w)
(world-sms w)
(world-enabled? w))]
[else
w]))
(define (send-report w)
(cond [(should-send-report? w)
(list (make-effect:send-sms
(world-sms w)
(string-append (description w)
"\n"
(maps-url
(world-loc w))))
(make-effect:beep))]
[else
'()]))
(define (closest-place loc)
(cond [(empty? (find-places ALL-PLACES loc))
(make-place "Unknown" loc 0)]
[else
(choose-smallest
(find-places ALL-PLACES loc))]))
(define (should-send-report? w)
(and (not (string-whitespace? (world-sms w)))
(world-enabled? w)
(not (eq? (world-closest w) UNINITIALIZED))
(or (eq? (world-last-reported w) UNINITIALIZED)
(place-has-transitioned?
(world-closest w)
(world-last-reported w)))))
(define (place-has-transitioned? place-1 place-2)
(cond
[(and (place-uninitialized? place-1)
(place-uninitialized? place-2))
false]
[(or (place-uninitialized? place-1)
(place-uninitialized? place-2))
true]
[(and (place-unknown? place-1)
(place-unknown? place-2))
false]
[(and (place-unknown? place-1)
(not (place-unknown? place-2)))
true]
[(and (not (place-unknown? place-1))
(place-unknown? place-2))
true]
[(and (not (place-unknown? place-1))
(not (place-unknown? place-2)))
(not (string=? (place-name place-1)
(place-name place-2)))]))
(define (maps-url a-loc)
(string-append "http://maps.google.com/maps?q="
(number->string
(exact->inexact (loc-lat a-loc)))
",+"
(number->string
(exact->inexact (loc-long a-loc)))
"&iwloc=A&hl=en"))
(define (description w)
(cond [(place-uninitialized? (world-closest w))
"Uninitialized"]
[else
(place-name (world-closest w))]))
(define (choose-smallest places)
(cond
[(empty? (rest places))
(first places)]
[(< (place-radius (first places))
(place-radius (second places)))
(choose-smallest
(cons (first places) (rest (rest places))))]
[else
(choose-smallest (rest places))]))
(define (find-places places a-loc)
(cond
[(empty? places)
empty]
[(place-matches? (first places) a-loc)
(cons (first places)
(find-places (rest places) a-loc))]
[else
(find-places (rest places) a-loc)]))
(define (place-matches? a-place a-loc)
(cond [(place-uninitialized? a-place)
false]
[else
(<= (location-distance (loc-lat a-loc)
(loc-long a-loc)
(loc-lat (place-loc a-place))
(loc-long (place-loc a-place)))
(place-radius a-place))]))
(define (loc->string w)
(string-append "("
(number->string (loc-lat w))
", "
(number->string (loc-long w))
")"))
(define (draw w)
(list (js-div '(("id" "main")))
(list (js-p '(("id" "aPara")))
(list (js-text "Currently at: "))
(list (js-text
(description w)))
(list (js-text " "))
(list (js-text
(loc->string (world-loc w)))))
(list (js-div)
(list (js-text "Notify SMS #"))
(list sms-input-dom)
(list (js-button world-enable)
(list (js-text "Use this number"))))
(list (js-p '(("id" "anotherPara")))
(list (js-text
(cond
[(world-enabled? w)
(format
"~a will be used for notification." (world-sms w))]
[else
"SMS Number has not been assigned"]))))
(list (js-p '(("id" "lastPara")))
(list (js-text
(cond [(place-uninitialized?
(world-last-reported w))
"No notification has been sent yet."]
[(place-unknown?
(world-last-reported w))
(format "Notification was last sent at ~s ~a."
(place-name (world-last-reported w))
(loc->string (place-loc (world-last-reported w))))]
[else
(format "Notification was last sent at ~s."
(place-name (world-last-reported w)))]))))))
(define (draw-css w)
'(("aPara" ("font-size" "30px"))
("anotherPara" ("font-size" "25px"))
("main" ("border-style" "solid"))))
(define (update-world-sms w sms)
(make-world (world-loc w)
(world-closest w)
(world-last-reported w)
sms
(world-enabled? w)))
(define sms-input-dom
(js-input "text" update-world-sms '(("id" "sms-input"))))
(define (world-enable w)
(make-world (world-loc w)
(world-closest w)
(world-last-reported w)
(world-sms w)
true))
(define (parse-places xexpr)
(cond
[(or (empty? xexpr)
(and (string? xexpr)
(string=? xexpr "")))
empty]
[else
(parse-items
(sxml-find-children
'item
(sxml-children
(first (sxml-find-children
'channel
(sxml-children xexpr))))))]))
(define (parse-items xexprs)
(cond
[(empty? xexprs)
empty]
[else
(cons (parse-item (first xexprs))
(parse-items (rest xexprs)))]))
(define (parse-item xexpr)
(local [(define (get-description-text x)
(cond
[(string=? x "")
""]
[else
(sxml-text (xml->s-exp (string-append "<top>" x "</top>")))]))]
(make-place (sxml-text (first (sxml-find-children 'title (sxml-children xexpr))))
(cond
[(empty? (sxml-find-children 'georss:point (sxml-children xexpr)))
(make-loc 0 0)]
[else
(parse-georss:point
(first (sxml-find-children 'georss:point (sxml-children xexpr))))])
(cond
[(empty? (sxml-find-children 'description (sxml-children xexpr)))
100]
[else
(loosely-parse-number
(apply string-append
(map get-description-text
(sxml-children (first (sxml-find-children 'description (sxml-children xexpr)))))))]))))
(define (loosely-parse-number an-str)
(cond [(empty? (split-whitespace an-str))
0]
[(number? (string->number (first (split-whitespace an-str))))
(string->number (first (split-whitespace an-str)))]
[else
0]))
(define (parse-georss:point xexpr)
(make-loc (string->number (first (split-whitespace (sxml-text xexpr))))
(string->number (second (split-whitespace (sxml-text xexpr))))))
(define (sxml-children a-sxml)
(cond
[(string? a-sxml)
(error 'children "Can't have children of a string xexpr")]
[else
(rest (rest a-sxml))]))
(define (sxml-text a-sxml)
(local [ (define (get-text* xexprs)
(cond
[(empty? xexprs)
""]
[else
(string-append (sxml-text (first xexprs))
(get-text* (rest xexprs)))]))]
(cond
[(string? a-sxml)
a-sxml]
[(pair? a-sxml)
(get-text* (sxml-children a-sxml))]
[(empty? a-sxml)
""])))
(define (sxml-find-children name children)
(cond [(empty? children)
empty]
[else
(cond [(string? (first children))
(sxml-find-children name (rest children))]
[(pair? (first children))
(cond
[(symbol=? name (first (first children)))
(cons (first children)
(sxml-find-children name (rest children)))]
[else
(sxml-find-children name (rest children))])]
[else
(error 'find-children children)])]))
(define (split-whitespace str)
(local [(define (splitter letters-so-far source)
(cond
[(empty? source)
(cond [(empty? letters-so-far)
empty]
[else
(list (list->string (reverse letters-so-far)))])]
[(char-whitespace? (first source))
(cond
[(empty? letters-so-far)
(splitter empty (rest source))]
[else
(cons (list->string (reverse letters-so-far))
(splitter empty (rest source)))])]
[else
(splitter (cons (first source) letters-so-far)
(rest source))]))]
(splitter empty (string->list str))))
(define ALL-PLACES
(parse-places
(xml->s-exp (get-url MYMAPS-URL))))
(define TICK-DELAY (* 5 60))
(js-big-bang initial-world
(on-location-change move)
(on-tick! TICK-DELAY
record-reporting send-report)
(on-draw draw draw-css))