-- crowd of functional programmers

import Numeric
type Vect = (Float, Float)
type Figure = [LineSegment]
type LineSegment = [Vect]
type Frame = (Vect,Vect,Vect)
type Painter = (Frame -> IO ())
blank =  \ frame -> putStr ""

infixr 7 +~, -~ 
infixr 8 *~
(+~), (-~) :: Vect -> Vect -> Vect
(x0,y0) +~ (x1,y1) = (x0+x1, y0+y1)        -- addVect
(x0,y0) -~ (x1,y1) = (x0-x1, y0-y1)        -- subVect
(*~) :: Float -> Vect ->Vect
a *~ (x,y) = (a*x, a*y)                    -- scaleVect

man :: Painter
man = segmentsToPainter 20 20
    [[(6,10),(0,10),(0,12),(6,12),(6,14),(4,16),(4,18),(6,20),(8,20),(10,18),
      (10,16),(8,14),(8,12),(10,12),(10,14),(12,14),(12,10),(8,10),(8,8),
      (10,0),(8,0),(7,4),(6,0),(4,0),(6,8),(6,10)]]

unitSquare :: Frame
unitSquare = ((128,16),(256,0),(0,256))

drawLine,drawLine' :: [Vect] -> String
drawLine ((x,y):xys) = show x ++ " " ++ show y ++ " moveto\n" ++ drawLine' xys
drawLine' [] = ""
drawLine' ((x,y):xys) = show x ++ " " ++ show y ++ " lineto\n" ++ drawLine' xys

segmentsToPainter :: Float -> Float -> Figure -> Painter
segmentsToPainter scale0 scale1 segs =
 \ frame -> putStr $
            let toFrame (x,y) = frameCoodMap frame (x/scale0, y/scale1)
                drawSeg seg = drawLine (map toFrame seg)
             in
              concat (map drawSeg segs) ++ "stroke\n"

frameCoodMap :: Frame -> (Vect -> Vect)
frameCoodMap (org,edge0,edge1) = 
 \ (x,y) -> org +~ x *~ edge0 +~ y *~ edge1

transformPainter :: Painter -> Vect -> Vect -> Vect -> Painter
transformPainter painter org edge0 edge1 =
 \ frame ->
    let m = frameCoodMap frame
        newOrg = m org
        newEdge0 = m edge0 -~ newOrg
        newEdge1 = m edge1 -~ newOrg
      in painter (newOrg, newEdge0, newEdge1)
above,beside :: Float -> Float -> Painter -> Painter -> Painter
above m n p q = 
 \ frame -> do transformPainter p (0,r) (1,r) (0,1) frame
               transformPainter q (0,0) (1,0) (0,r) frame
      where r = n/(m+n)
beside m n p q =
 \ frame -> do transformPainter p (0,0) (r,0) (0,1) frame
               transformPainter q (r,0) (1,0) (r,1) frame
      where r = m/(m+n)
horiz :: Float -> Painter -> Painter
horiz 0 _ = blank
horiz n p = beside 1 (n-1) p (horiz (n-1) p)

crowd :: Painter -> Painter
crowd p = above (1-s10) s10 blank
           (above (inv 10) s9 (horiz 10 p)
             (above (inv 9) s8 (horiz 9 p)
               (above (inv 8) s7 (horiz 8 p)
                 (above (inv 7) s6 (horiz 7 p)
                   (above (inv 6) (inv 5) (horiz 6 p) (horiz 5 p))))))
            where inv n = fromRational (1/n)
                  s10 = sum (map inv [5..10])
                  s9 = sum (map inv [5..9])
                  s8 = sum (map inv [5..8])
                  s7 = sum (map inv [5..7])
                  s6 = sum (map inv [5..6])

main = crowd man unitSquare

