content: {-# LANGUAGE OverloadedStrings #-}
  import Reflex
  import Reflex.Dom
  import Reflex.Dom.Time
  import Data.Text (Text, pack)
  import Data.Map (Map, fromList)
  import Data.Time.Clock (getCurrentTime)
  import Control.Monad.Trans (liftIO)
  
  type Point = (Float,Float)
  type Segment = (Point,Point)
  
  main = mainWidget $ do
  
    -- An event that fires every 0.05 seconds.
    dTick <- tickLossy 0.05 =<< liftIO getCurrentTime
  
    -- A dynamically updating counter.
    dCounter <- foldDyn (\_ c -> c+1) (0::Int) dTick
  
    let
        -- A dynamically updating angle.
        dAngle = fmap (\c -> fromIntegral c / 800.0) dCounter
  
        -- A dynamically updating spiral
        dSpiralMap = fmap toSpiralMap dAngle
  
        -- svg parameters
        width = 600
        height = 600
  
        boardAttrs =
           fromList [ ("width" , pack $ show width)
                    , ("height", pack $ show height)
                    , ("viewBox", pack $ show (-width/2) ++ " " ++ show (-height/2) ++ " " ++ show width ++ " " ++ show height)
                    ]
  
    elAttr "h1" ("style" =: "color:black") $ text "Polyspiral"
    elAttr "a" ("href" =: "http://rosettacode.org/wiki/Polyspiral#Haskell") $ text "Rosetta Code / Polyspiral / Haskell"
  
    el "br" $ return ()
    elSvgns "svg" (constDyn boardAttrs) (listWithKey dSpiralMap showLine)
  
    return ()
  
  -- The svg attributes needed to display a line segment.
  lineAttrs :: Segment -> Map Text Text
  lineAttrs ((x1,y1), (x2,y2)) =
    fromList [ ( "x1",    pack $ show x1)
             , ( "y1",    pack $ show y1)
             , ( "x2",    pack $ show x2)
             , ( "y2",    pack $ show y2)
             , ( "style", "stroke:blue")
             ]
  
  -- Use svg to display a line segment.
  showLine :: MonadWidget t m => Int -> Dynamic t Segment -> m ()
  showLine _ dSegment = elSvgns "line" (lineAttrs <$> dSegment) $ return ()
  
  -- Given a point and distance/bearing , get the next point
  advance :: Float -> (Point, Float, Float) -> (Point, Float, Float)
  advance angle ((x,y), len, rot) =
    let new_x = x + len * cos rot
        new_y = y + len * sin rot
        new_len = len + 3.0
        new_rot = rot + angle
    in ((new_x, new_y), new_len, new_rot)
  
  -- Given an angle, generate a map of segments that form a spiral.
  toSpiralMap :: Float -> Map Int ((Float,Float),(Float,Float))
  toSpiralMap angle =
        fromList                       -- changes list to map (for listWithKey)
    $   zip [0..]                      -- annotates segments with index
    $   (\pts -> zip pts $ tail pts)   -- from points to line segments
    $   take 80                        -- limit the number of points
    $   (\(pt,_,_) -> pt)              -- cull out the (x,y) values
    <$> iterate (advance angle) ((0, 0), 0, 0)  -- compute the spiral
  
  -- Display an element in svg namespace
  elSvgns :: MonadWidget t m => Text -> Dynamic t (Map Text Text) -> m a -> m a
  elSvgns t m ma = do
      (el, val) <- elDynAttrNS' (Just "http://www.w3.org/2000/svg") t m ma
      return val
  