--
import Shape
import Draw
import Region
import Picture
import SOEGraphics hiding (Region)
import qualified SOEGraphics as G (Region)
import Win32Misc (timeGetTime)
import Word (Word32)

-------------- The animation stuff ----------


word32ToInt :: Word32 -> Int
word32ToInt x = fromInteger(toInteger x)

type Animation a = Time -> a
type Time = Float

rubberBall :: Animation Shape
rubberBall t = Ellipse (sin t) (cos t)

revolvingBall :: Animation Region
revolvingBall t
  = let ball = Shape (Ellipse 0.2 0.2)
    in Translate (sin t, cos t) ball

planets :: Animation Picture
planets t
  = let p1 = Region Red (Shape (rubberBall t))
        p2 = Region Yellow (revolvingBall t)
    in p1 `Over` p2

tellTime :: Animation String
tellTime t = "The time is: " ++ show t

{-
openWindowEx :: String -> Maybe Point -> Maybe Point ->
                (Graphic -> DrawFun) -> Maybe Word32 ->
                IO Window
-}

animate :: String -> Animation a -> (a -> IO Graphic) -> IO ()
animate title anim toGraphic
  = runGraphics (
    do w <- openWindowEx title (Just (0,0)) (Just (xWin,yWin))
              drawBufferedGraphic (Just 30)
       t0 <- timeGetTime
       let loop =
             do t <- timeGetTime
                -- print ("the time is" ++ (show t))
                let ft = intToFloat (word32ToInt (t-t0)) / 1000
                -- print "past let"
                gr <- toGraphic (anim ft)
                -- print "past toGraphic"
                setGraphic w gr
                getWindowTick w
                loop
       loop
    )



main1 = animate "Animation of a Shape" rubberBall
         (return . withColor Blue . shapeToGraphic)

main2 = animate "Animated Text" tellTime
         (return . text (100,200))

main3 = animate "Animated Region" revolvingBall
  (\r -> return (withColor Yellow
                          (regionToGraphic r)))

main4 = animate "Animated Picture" planets (return.picToGraphic)

main4a :: IO ()
main4a = animate "Experiment" both (return.picToGraphic)
 where both t = Over (Text (1,2) (tellTime t)) (planets t)




regionToGraphic :: Region -> Graphic
regionToGraphic = drawRegion . regionToGRegion

picToGraphic :: Picture -> Graphic
picToGraphic (Region c r)
  = withColor c (regionToGraphic r)
picToGraphic (p1 `Over` p2)
  = picToGraphic p1 `overGraphic` picToGraphic p2
picToGraphic (Text v str) = (text (trans v) str)
picToGraphic EmptyPic = emptyGraphic



type Anim = Animation Picture

emptyA :: Anim
emptyA t = EmptyPic
overA :: Anim -> Anim -> Anim
overA a1 a2 t = a1 t `Over` a2 t

overManyA :: [Anim] -> Anim
overManyA = foldr overA emptyA

-- timeTransA :: (Time -> Time) -> Animation a -> Animation a
timeTransA :: Animation Time -> Animation a -> Animation a

-- timeTransA f a t = a (f t)
timeTransA f a = a . f

{-
  timeTransA (2*) anim

  timeTransA (5+) anim

  timeTransA negate anim
-}

rBall :: Anim
rBall t = let ball = Shape (Ellipse 0.2 0.2)
          in Region Red (Translate (sin t, cos t) ball)

rBalls :: Anim
rBalls = overManyA [ timeTransA ((t*pi/4)+) rBall | t <- [0..7]]

main5 = animate "Lots of Balls" rBalls   (return . picToGraphic)

------- Type Classes and Animations ---------

newtype Behavior a = Beh (Time -> a)

lift0 :: a -> Behavior a
lift0 x = Beh (\t -> x)

lift1 :: (a -> b) -> (Behavior a -> Behavior b)
lift1 f (Beh a)
  = Beh (\t -> f (a t))

lift2 :: (a -> b -> c) -> (Behavior a -> Behavior b -> Behavior c)
lift2 g (Beh a) (Beh b)
  = Beh (\t -> g (a t) (b t))

lift3 :: (a -> b -> c -> d) ->
          (Behavior a -> Behavior b -> Behavior c -> Behavior d)
lift3 g (Beh a) (Beh b) (Beh c)
   = Beh (\t -> g (a t) (b t) (c t))

instance Eq (Behavior a) where
  a1 == a2 = error "Can't compare animations."

instance Show (Behavior a)  where
   showsPrec n a1 = error "Can't coerce animation to String."

instance Num a => Num (Behavior a) where
  (+) = lift2 (+);  (*) = lift2 (*)
  negate = lift1 negate; abs = lift1 abs
  signum = lift1 signum
  fromInteger = lift0 . fromInteger

instance Fractional a => Fractional (Behavior a) where
  (/) = lift2 (/)
  fromRational = lift0 . fromRational

instance Floating a => Floating (Behavior a) where
   pi    = lift0 pi;    sqrt = lift1 sqrt
   exp   = lift1 exp;   log = lift1 log
   sin   = lift1 sin;   cos = lift1 cos
   tan   = lift1 tan
   asin  = lift1 asin;  acos = lift1 acos
   atan  = lift1 atan
   sinh  = lift1 sinh;  cosh = lift1 cosh
   tanh  = lift1 tanh
   asinh = lift1 asinh; acosh = lift1 acosh
   atanh = lift1 atanh

time :: Behavior Time
time = Beh (\t -> t)

instance Ani [a] where
  empty = []
  over  = (++)

instance Ani (Fun a) where
  empty = Fun id
  Fun a `over` Fun b = Fun (a . b)

data Fun a = Fun (a->a)

class Ani a where
  empty :: a
  over  :: a -> a -> a

instance Ani a => Ani (Float -> a) where
  empty t = empty
  over f g t = over (f t) (g t)

instance Ani Picture where
  empty = EmptyPic
  over  = Over

instance Ani a => Ani (Behavior a) where
  empty = lift0 empty
  over  = lift2 over

overMany :: Ani a => [a] -> a
overMany = foldr over empty


m :: Behavior Picture
m = let a = lift0 (empty `over` p)
    in a `over` empty

p :: Picture
p = empty

timeTrans (Beh t) (Beh a) = Beh (timeTransA t a)

reg   = lift2 Region
shape = lift1 Shape
ell   = lift2 Ellipse
red   = lift0 Red
blue  = lift0 Blue
translate (Beh a1, Beh a2) (Beh p)
      = Beh (\t -> Translate (a1 t, a2 t) (p t))

rBall2 :: Behavior Picture
rBall2
  = let ball = shape (ell 0.2 0.2)
    in reg red (translate (sin time, cos time) ball)

(>*) :: Ord a => Behavior a -> Behavior a -> Behavior Bool
(>*) = lift2 (>)

ifFun :: Bool -> a -> a -> a
ifFun p c a = if p then c else a

cond :: Behavior Bool -> Behavior a -> Behavior a -> Behavior a
cond = lift3 ifFun

rb :: Behavior Color
rb = cond (sin time >* 0) red blue

animate2 :: String -> Behavior Picture -> IO ()
animate2 s (Beh pf) =
  animate s pf (return . picToGraphic)

main6 = animate2 "Wild" rBall2

----- Kaleidoscope example

class Turnable a where
  turn :: Float -> a -> a

instance Turnable Picture where
  turn theta (Region c r) = Region c (turn theta r)  -- turn on Regions
  turn theta (p1 `Over` p2) = turn theta p1 `Over` turn theta p2
  turn theta EmptyPic = EmptyPic

instance Turnable a => Turnable (Behavior a) where
  turn theta (Beh b) = Beh(turn theta . b)

rotate :: Float -> Coordinate -> Coordinate
rotate theta (x,y) =
    (x*c + y*s, y*c - x*s)
    where (s,c) = (sin theta,cos theta)

instance Turnable Shape where
  turn theta (Polygon ps) = Polygon (map (rotate theta) ps)
  -- lots of missing cases here for
  -- turn theta (Rectangle s1 s2)  =
  -- turn theta (Ellipse r1 r2)    =
  -- turn theta (RtTriangle s1 s2) =

instance Turnable Region where
  turn theta (Shape sh) = Shape (turn theta sh)
   -- lots of missing cases here for
   -- turn theta (Translate (u,v) r)   =
   -- turn theta (Scale (u,v) r)       =
   -- turn theta (Complement r)        =
   -- turn theta (r1 `Union` r2)       =
   -- turn theta (r1 `Intersect` r2)   =
   -- turn theta Empty = Empty

spectrum = [ c | c <- [minBound..], c/= Black]  -- All colors but black

slowTime = 0.1 * time
kaleido :: Integer -> (Float -> Behavior Coordinate) -> Behavior Picture
kaleido n f =
   lift2 turn (pi * sin slowTime)
     (overMany (zipWith reg (map lift0 (cycle spectrum))
                            (map (flip turn poly) rads)))
  where rads = map (((2 * pi/fromInteger n)*).fromInteger) [0..n-1]
        poly = polyShapeAnim (map f rads)

polyShapeAnim :: [Behavior Coordinate] -> Behavior Region
polyShapeAnim = lift1 (Shape . Polygon).synclist
  where synclist :: [Behavior a] -> Behavior[a]
        synclist l = Beh(\t -> map (\(Beh f) -> f t) l)

syncPair :: (Behavior a,Behavior b) -> Behavior(a,b)
syncPair (Beh f,Beh g) = Beh(\t -> (f t,g t))

kaleido1 = kaleido 6 star
  where star x = syncPair (2*cos(v*c+l),2 * abs(sin(slowTime * s - l)))
         where v = lift0 x
               l = v * (slowTime + 1)
               (s,c) = (sin l,cos l)

animateB :: String -> Behavior Picture -> IO ()
animateB s (Beh x) = animate s x
   (return . picToGraphic)

main7 =  animateB "kaleido1" kaleido1
--