-- this file is an exercise in working with functions
-- not everything here is sensible, but it's all good exercise

data Shape a = Circle a |
               Square a |
               Rectangle a a 
   deriving (Show)

-- a sample of shapes
set1 :: [Shape Double]
set1 = [Circle 1 
       ,Square 2 
       ,Rectangle 3 4
       ]

-- compute the area of a Shape
area :: Floating a => Shape a -> a

area (Circle r) = pi * r * r
area (Square l) = l * l
area (Rectangle w h) = w * h

-- change the scale of a Shape
scaleShape :: Floating a => a -> Shape a -> Shape a

scaleShape s (Circle r) = Circle (r*s)
scaleShape s (Square l) = Square (l*s)
scaleShape s (Rectangle l w) = Rectangle (l*s) (w*s) 

scale_all s = map (scaleShape s)
total_area = foldr ((+) . area) 0
max_area   = foldr (max . area) 0

-- a type for points in a 2D "plane"
-- polymorphic, to allow for various purposes

type Point a = (a,a)

-- move a point

translate_point :: Floating a => Point a -> Point a -> Point a
translate_point (x,y) (u,v) = (x+u,y+v)

-- ------------------------------------------
-- make Shapes placeable in a Cartesian plane
-- ------------------------------------------

data MoveableShape a = At (Shape a) (Point a) 
  deriving (Show)

-- accessors for convenience
getShape (At s p) = s
getPoint (At s p) = p

-- a sample 
set2 :: [MoveableShape Double]
set2 = [At (Circle 1) (0,0) 
       ,At (Square 2) (1,1) 
       ,At (Rectangle 3 4) (-1,1) 
       ]

-- revisions to Shape methods, specialized for MoveabeShapes
-- note the simplicity: just add another function in the "pipeline"

total_area2 = foldr ((+) . area . getShape) 0
max_area2   = foldr (max . area . getShape) 0

-- somethign a little different: counting shapes in
-- a list

count = foldr (countShapes . getShape) (0,0,0) 
  where countShapes (Circle _)      (a,b,c) = (a+1,b,c)
        countShapes (Square _)      (a,b,c) = (a,b+1,c)
        countShapes (Rectangle _ _) (a,b,c) = (a,b,c+1)

--------------------------------------
-- methoods uniqie to MoveableShape
--------------------------------------

-- move a single shape around
translate :: Floating a => Point a -> MoveableShape a -> MoveableShape a

translate t (At s p) = At s (translate_point t p) 
scale2    x (At s p) = At (scaleShape x s) p

-- the above is very simple, but looks a lot like function
-- composition.  The complication is that (At) is a function
-- of 2 arguments, so applying (.) is a bit tricky.
-- However, we can try to generalize (.) with something like
-- the following:

xform f g (At s p) = At (f s) (g p)

tr2 t = xform id (translate_point t)
sc2 x = xform (scaleShape x) id

-- trivial implementations of translate and scale for
-- lists of moveableshapes
-- it's useful to point out how much simplere this code is
-- than the for-loops needed in Java, e.g.

translate_all p = map (translate p)
scale_all2 s = map (scale2 s)





