module Medium where

import qualified Medium.Temporal as Temporal


infixr 7 +:+  {- like multiplication -}
infixr 6 =:=  {- like addition -}


class Construct medium where
   prim :: a -> medium a

   {- for easy compatibility with Haskore 2000 songs
      replace :+: by +:+ and :=: by =:= -}
   (+:+), (=:=) :: medium a -> medium a -> medium a

   serial, parallel :: Temporal.C a => [medium a] -> medium a
   serial1, parallel1 :: [medium a] -> medium a

class Construct medium => C medium where
   {- Do actions on each (virtual) constructor, don't recurse. -}
   switchBinary ::
         (a -> b) -> (medium a -> medium a -> b) -> (medium a -> medium a -> b)
      -> (b -> medium a -> b)
   switchList :: (a -> b) -> ([medium a] -> b) -> ([medium a] -> b)
      -> medium a -> b


{- A variant of fmap that does not only allow manipulation of primitives
   but also of the compositions.
   Though the structure must be preserved. -}
mapList :: (Temporal.C b, Medium.C medium) =>
   (a->b) -> ([medium b]->[medium b]) -> ([medium b]->[medium b]) -> medium a -> medium b
mapList f g h = foldList (prim . f) (serial . g) (parallel . h)

mapListFlat :: (Temporal.C b, Medium.C medium) =>
   (a -> b) -> ([medium a] -> [medium b]) -> ([medium a] -> [medium b]) -> medium a -> medium b
mapListFlat f g h = switchList (prim . f) (serial . g) (parallel . h)


{- This is even more general than mapList -}
foldList :: Medium.C medium => (a->b) -> ([b]->b) -> ([b]->b) -> medium a -> b
foldList f g h =
   let recurse = map (foldList f g h)
   in  switchList f (g . recurse) (h . recurse)

foldBin :: Medium.C medium => (a->b) -> (b->b->b) -> (b->b->b) -> b -> medium a -> b
foldBin f g h z =
   -- foldList f (foldr1 g) (foldr1 h)
   -- this implementation preserves the structure of the binary tree
   let recurse op x y = foldBin f g h z x `op` foldBin f g h z y
   in  switchBinary f (recurse g) (recurse h) z


listMediumFromAny :: (Construct dst, C src, Temporal.C a) => src a -> dst a
listMediumFromAny  =  foldList prim serial parallel

binaryMediumFromAny :: (Construct dst, C src) => dst a -> src a -> dst a
binaryMediumFromAny z  =  foldBin prim (+:+) (=:=) z
