module CloudPacker (diagram) where

-- Cobbled together from snippets and ideas by Chris Done
-- http://github.com/chrisdone/wordcloud/

import Control.Arrow ((&&&), (***), first)
import Control.Monad (liftM)
import Data.Char (isLetter, toLower)
import Data.List (init, sortBy, foldl')
import Data.Maybe (listToMaybe)
import Data.Ord (comparing)
import qualified Data.Map as M

import Graphics.Rendering.Diagrams
import Graphics.Rendering.Diagrams.Engine (sizeAndPos)

type Point = (Int,Int)
type Size = Point
type Rect = (Point,Point)

-- Arrange the text boxes on the page, starting with the
-- largest and placing each one in the first gap available.
diagram :: [Diagram] -> Diagram
diagram = arrange . uncurry zip . (f &&& id)
    where f = map toPoint . foldl addToLayout [] . map size

arrange :: [(Point,Diagram)] -> Diagram
arrange = positionA left top . map (first (fromIntegral *** fromIntegral))

addToLayout :: [Rect] -> Size -> [Rect]
addToLayout [] sz = let (w,h) = sz in [toRect (-w`div`2,-h`div`2) sz]
addToLayout rs sz = maybe rs (\p -> rs ++ [toRect p sz]) $ listToMaybe $ bestfits sz rs

bestfits :: Size -> [Rect] -> [Point]
bestfits sz rs = concatMap (\r -> aroundRect sz r rs) rs

aroundRect :: Size -> Rect -> [Rect] -> [Point]
aroundRect sz r rs = filter valid (potentials sz r)
    where valid pt = not (any (overlaps (toRect pt sz)) rs)

toRect :: Point -> Size -> Rect
toRect (x,y) (w,h) = ((x,y),(x+w,y+h))
toPoint :: Rect -> Point
toPoint = fst

-- Produces candidates in anti-clockwise order.
potentials :: Size -> Rect -> [Point]
potentials (w,h) ((x1,y1),(x2,y2)) = concat [leftside,rightside,bottomside,topside]
    where leftside = init $ map ((,) x) [y..y2]
          rightside = init $ map ((,) x2) [y2,y2-1..y]
          bottomside = init $ map (flip (,) y2) [x..x2]
          topside = init $ map (flip (,) y) [x2,x2-1..x]
          (x,y) = (x1 - w, y1 - h)

overlaps :: Rect -> Rect -> Bool
overlaps r1 r2 = r1 `overlapX` r2 && r1 `overlapY` r2
overlapX :: Rect -> Rect -> Bool
overlapX ((x1,_),(x1',_)) ((x2,_),(x2',_)) = if x1 < x2 then x1' > x2 else x2' > x1
overlapY :: Rect -> Rect -> Bool
overlapY ((_,y1),(_,y1')) ((_,y2),(_,y2')) = if y1 < y2 then y1' > y2 else y2' > y1

size :: Diagram -> Size
size = (ceiling *** ceiling) . fst . sizeAndPos

