{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
module Temporal.Media (
	-- * Time classes
	Dur(..), Temporal(..), Stretchable(..), 
	ToMaybe(..), TemporalFunctor(..), 
    Sustainable(..), sustain,
    TemporalStretchable(..),
    -- * Transformers
	Reversible(..), Sliceable(..), cut, 
	-- * Structure 
	Construct(..), Arrangeable(..),	Controlable(..),
	sequent, parallel, loop, delay, temp,
	-- * Media
	Media(..), fold, fromMedia,
	-- * Simple interperetation
	-- ** Event list
	Event, EventList(..), 
	mapEvent, toEvent, toEventList,
	-- ** Unit Temporal Media
	MediaUnit(..), unMediaUnit, foldU, fromMediaUnit,
	Unit(..),
    -- ** Misc
     tmapRel, dmapRel, tdmapRel,  tstretchRel, linseg
)
where

import Control.Applicative
import Control.Monad

import Data.Function
import Data.Ratio
import Data.Tree

import Prelude hiding (reverse, take, drop)
import qualified Prelude as P (reverse)

import Debug.Trace

debug msg x = trace (msg ++ " : " ++ show x) x

-------------------------------------------------
-- Classes
--
-------------------------------------------------
-- Time classes

-- | time class
class (Num t, Ord t, Fractional t) => Dur t

instance Dur Double
instance Integral a => Dur (Ratio a)

-- | Temporal structures
class Dur t => Temporal t a where
	none    :: t -> a        -- ^ absence of value
	dur     :: a -> t        -- ^ duration of value            

-- | Stretching values by given time-factor
class Temporal t a => Stretchable t a where
	stretch :: t -> a -> a       

-- | Values covertible to 'Maybe'	
--
--  auxiliary class for conversion to 'EventList'
class ToMaybe m where
	toMaybe :: m a -> Maybe a

-- | temporal map
--
-- minimal complete defenition : @tdmap@
class Dur t => TemporalFunctor t f where
    -- | map with time
    tmap :: (t -> a -> b) -> f a -> f b   
    -- | map with duration
    dmap :: (t -> a -> b) -> f a -> f b 

    -- | map with time and duration
    tdmap  :: (t -> t -> a -> b) -> f a -> f b

    tmap f = tdmap (flip $ const f)
    dmap f = tdmap (const f)

class Dur t => Sustainable t f where    
    -- | map with time and duration and transform duration time
    sustainBy :: (t -> t -> a -> (b, t)) -> f a -> f b

-- | adds constant amount of duration to all notes
sustain :: (Dur t, Sustainable t f) => t -> f a -> f a
sustain dt' = sustainBy $ \t dt x -> (x, dt + dt')


class Stretchable t a => TemporalStretchable t a where
    tstretch :: (t -> t) -> a -> a



---------------------------------------------------
-- transformers
--

class Reversible a where
	reverse :: a -> a

-- | extracting parts, minimal complete definition:  'slice'.
class Temporal t a => Sliceable t a where
	slice :: t -> t -> a -> a   -- ^ @slice t0 t1 v@ extracts part of @v@ inside @[t0, t1]@
	take  :: t -> a -> a        
	drop  :: t -> a -> a

	take t   = slice 0 t
	drop t x = slice t (dur x) x

sliceErrorMessage = error "should be t0 <= t1, for slice t0 t1" 

-- | mixing slice and reverse. 
--
-- @cut t0 t1 v@ - if @t1 < t0@ reverses result of 'slice'
cut :: (Reversible a, Sliceable t a) => t -> t -> a -> a
cut t0 t1 m
	| t0 <= t1  = slice t0 t1 m
	| otherwise = slice (tm - t0) (tm - t1) $ reverse m
	where tm = dur m

----------------------------------------------------
-- Structure
--

-- | constructor for generic structures
class Construct m where
	prim :: a -> m a

-- | composing structures in sequent and parallel ways 
class Arrangeable a where
	(+:+) :: a -> a -> a
	(=:=) :: a -> a -> a

-- | modifer
class Controlable c a where
	control :: c -> a -> a
	

sequent, parallel :: Arrangeable a => [a] -> a

sequent  = foldl1 (+:+)
parallel = foldl1 (=:=)

loop :: Arrangeable a => Int -> a -> a
loop n = sequent . replicate n 

delay :: (Temporal t a, Arrangeable a) => t -> a -> a
delay t x = none t +:+ x


-- | constructs generic temporal structure @m a@ form time @t@ and initial value @a@
temp :: (Construct m, Temporal t (m a), Stretchable t (m a))
	=> t -> a -> m a
temp t = stretch t . prim


----------------------------------------------------
-- Media

-- | Data type to represent temporal media
data Media c a = Prim a                        -- ^ single value
	       | Media c a :+: Media c a       -- ^ sequential composition
	       | Media c a :=: Media c a       -- ^ parallel composition
	       | Control c (Media c a)         -- ^ specific environment modifier
		deriving (Show, Eq)


-- | Folding Media
--
-- > fold prim seq par mod x
--
-- * prim - responds to 'Prim'
--
-- * seq  - responds to ':+:'
--
-- * par  - responds to ':=:'
--
-- * mod  - responds to 'Control'
fold :: (a -> b) -> (b -> b -> b) -> (b -> b -> b) -> (c -> b -> b) 
	-> Media c a -> b
fold prim seq par mod m = 
	case m of
		Prim a      -> prim a
		a :+: b     -> (seq `on` f) a b
		a :=: b     -> (par `on` f) a b
		Control c a -> mod c $ f a 
	where f = fold prim seq par mod
	


instance Functor (Media c) where
	fmap f x = case x of
			Prim a      -> Prim $ f a
			a :+: b     -> fmap f a :+: fmap f b
			a :=: b     -> fmap f a :=: fmap f b
			Control c a -> Control c $ fmap f a

instance Monad (Media c) where
	return   = Prim
	ma >>= f = case ma of
			Prim a      -> f a
			a :+: b     -> (a >>= f) :+: (b >>= f)
			a :=: b     -> (a >>= f) :=: (b >>= f)
			Control c a -> Control c $ a >>= f

instance Applicative (Media c) where
	pure  = return
	(<*>) = ap


-- time

instance Temporal t a => Temporal t (Media c a) where
	none      = Prim . none
	dur       = fold dur (+) max (const id)

instance Stretchable t a => Stretchable t (Media c a) where
	stretch d = fmap $ stretch d

instance Stretchable t a => TemporalStretchable t (Media c a) where
    tstretch f m = tdmapM (\t _ -> stretch $ f t) m
    
-- transformers

instance Reversible a => Reversible (Media c a) where
	reverse x = case x of
			Prim a      -> Prim $ reverse a
			a :+: b     -> ((:+:) `on` reverse) b a
			a :=: b     -> ((:=:) `on` reverse) a b
			Control c a -> Control c $ reverse a


instance Sliceable t a => Sliceable t (Media c a) where
	slice t0 t1 m 
	    | t1 < t0   = sliceErrorMessage
	    | t0 < 0    = none (abs t0) :+: slice 0 t1 m
	    | t1 > tm   = slice t0 tm m :+: none (t1 - tm)
	    | otherwise = case m of
		    		Prim a      -> Prim $ slice t0 t1 a
		    		a :+: b     -> sliceSeq t0 t1 a b 
				a :=: b     -> ((:=:) `on` slice t0 t1) a b
				Control c a -> Control c $ slice t0 t1 a
		where tm = dur m

sliceSeq :: Sliceable t a => t -> t -> Media c a -> Media c a -> Media c a
sliceSeq t0 t1 a b
	| t1 <= ta  = slice t0 t1 a
	| t0 >= ta  = slice (t0 - ta) (t1 - ta) b
	| otherwise = slice t0 ta a :+: slice 0 (t1 - ta) b
	where ta = dur a

	
-- Structure

instance Construct (Media c) where
	prim = Prim

instance Arrangeable (Media c a) where
	(+:+) = (:+:)
	(=:=) = (:=:)

instance Controlable c (Media c a) where
	control = Control

----------------------------------------------------
-- Meaning

-- | Media interpretation
--
-- given two functions (to convert elementary value @(a -> b)@, 
-- and to convert modifiers @(c -> b -> b)@) `fromMedia` interprets 'Media' structure
fromMedia :: Arrangeable b => (a -> b) -> (c -> b -> b) -> Media c a -> b
fromMedia prim mod = fold prim (+:+) (=:=) mod
	
--------------------------------------------------------------------------------------	
-- | Event type
--
-- @(t, dt, a)@ - value @a@ starts at @t@ and lasts for @dt@
type Event t a = (t, t, a)

-- | list of events with given total time
data EventList t a = EventList t [Event t a]
	deriving (Show, Eq)


toEvent :: (Temporal t (m a), ToMaybe m) => m a -> EventList t a
toEvent a = EventList (dur a) $ maybe [] (return . singleEvent) $ toMaybe a
	where singleEvent x = (0, dur a, x)

mapEvent :: Dur t => (a -> b) -> Event t a -> Event t b
mapEvent f (t, dt, a) = (t, dt, f a)

instance Dur t => Functor (EventList t) where
	fmap f (EventList t es) = EventList t $ fmap (mapEvent f) es

-- time

instance Dur t => Temporal t (EventList t a) where
	none t = EventList t []
	dur (EventList t _) = t

instance (Dur t, Stretchable t a) => Stretchable t (EventList t a) where
	stretch d (EventList t es) = EventList (d * t) $ map (stretchEvent d) es
		where stretchEvent d (t, dt, a) = (d * t, d * dt, stretch d a)

instance Dur t => TemporalFunctor t (EventList t) where
	tdmap f (EventList t es) = EventList t $ map (tmapEvent f) es
		where tmapEvent f (t, dt, a) = (t, dt, a') where a' = f t dt a

instance Dur t => Sustainable t (EventList t) where
	sustainBy f (EventList t es) = EventList t $ map (tmapEvent f) es
		where tmapEvent f (t, dt, a) = (t, dt', a') where (a', dt') = f t dt a



-- structure

instance Dur t => Construct (EventList t) where
	prim a = EventList 1 [(0, 1, a)]

instance Dur t => Arrangeable (EventList t a) where
	(EventList t es) +:+ (EventList t' es') = 
		EventList (t + t') (es ++ map (delayEvent t) es')
		where delayEvent d (t, dt, a) = (t + d, dt, a)
	(EventList t es) =:= (EventList t' es') =
		EventList (max t t') $ merge es es'
		where merge []  x = x
		      merge x  [] = x
                      merge (a@(ta, _, _):as) (b@(tb, _, _):bs) 
			| ta < tb   = a : merge as     (b:bs)
			| otherwise = b : merge (a:as) bs

instance Dur t => Controlable () (EventList t a) where
	control = const id

-- meaning

-- | converting to 'EventList'
--
-- 'toEventList' mapps generic temporal value @(m a)@ that can be 
-- represented with @(t, Maybe a)@ to 'EventList' 
toEventList :: (Temporal t (m a), ToMaybe m) 
	=> (c -> EventList t a -> EventList t a) 
	-> Media c (m a) -> EventList t a
toEventList = fromMedia toEvent

-------------------------------------------------------------------------
-------------------------------------------------------------------
-- Special case : Media with explicit time. Value is unit 
--

-- | Media with explicit time
--
-- Value is unit (undividable, invariant to reverse and time stretching) 
-- O(1) 'dur'
data Dur t => MediaUnit t c a = MediaUnit t (Media c (Unit t a))

unMediaUnit :: Dur t => MediaUnit t c a -> Media c (Unit t a)
unMediaUnit (MediaUnit _ m) = m


instance Dur t => Functor (MediaUnit t c) where
	fmap f (MediaUnit t m) = MediaUnit t $ fmap (fmap f) m


instance Dur t => Monad (MediaUnit t c) where
	return = MediaUnit 1 . return . return
	(MediaUnit t ma) >>= f = MediaUnit (dur ma') ma'
		where ma' = ft =<< ma
                      ft ta = case unMediaUnit . f <$> ta of
				(Unit t (Just a)) -> stretch t a
				(Unit t Nothing)  -> none t 

instance Dur t => Applicative (MediaUnit t c) where
	pure  = return
	(<*>) = ap


-- time 

instance Dur t => Temporal t (MediaUnit t c a) where
	none t              = MediaUnit t $ none t     
	dur (MediaUnit t _) = t

instance Dur t => Stretchable t (MediaUnit t c a) where
	stretch d (MediaUnit t m) = MediaUnit (t * d) $ stretch d m


instance Dur t => TemporalFunctor t (MediaUnit t c) where
    dmap f (MediaUnit t m) = MediaUnit t $ fmap (tmap f) m
    tdmap f (MediaUnit t m) = MediaUnit t $ tdmapM (\t dt -> fmap (f t dt)) m

instance Dur t => TemporalStretchable t (MediaUnit t c a) where
    tstretch f (MediaUnit _ m) = liftA2 MediaUnit dur id $ tstretch f m

instance Dur t => Sustainable t (MediaUnit t c) where
    sustainBy f (MediaUnit t m) = uncurry MediaUnit $ sustainByM f' m
        where f' t dt = liftA2 (,) (fu t dt) (ft t dt)
              ft t dt (Unit d a) = maybe dt (snd . f t dt) a  
              fu t dt = fmap (fst . f t dt) 


tdmapM :: Temporal t a => (t -> t -> a -> b) -> Media c a -> Media c b
tdmapM f m = fmap (\(t, dt, a) -> f t dt a) $ setEvents m

sustainByM :: (Stretchable t a, Stretchable t b) 
    => (t -> t -> a -> (b, t)) -> Media c a -> (t, Media c b)
sustainByM f m = (newDur t', rearrangeSustain t' m')
    where t' = setSustainDurs m'  
          m' = tdmapM (\t dt a -> (dt, f t dt a)) m

------------------------------------------------------------
-- time dependent mapping tools

setEvents :: Temporal t a => Media c a -> Media c (Event t a)
setEvents m = setTimes 0 (setDurs m) m

setDurs :: Temporal t a => Media c a -> Tree t
setDurs = fold prim seq par contr
    where prim  a   = Node (dur a) []
          seq   a b = Node (on (+) rootLabel a b) [a, b]
          par   a b = Node (on max rootLabel a b) [a, b]
          contr c a = Node (rootLabel a) [a]
         

setTimes :: Dur t => t -> Tree t -> Media c a -> Media c (Event t a)
setTimes t0 durTree m = 
    case m of
        Prim a  -> Prim (t0, rootLabel durTree, a)
        a :+: b -> setTimes t0 ta a :+: setTimes (t0 + rootLabel ta) tb b
        a :=: b -> on (:=:) (uncurry $ setTimes t0) (ta, a) (tb, b)
        Control c a -> Control c $ setTimes t0 ta a
    where sf = subForest durTree
          ta = sf !! 0
          tb = sf !! 1
    

rearrangeSustain :: Stretchable t a => Tree (t, t) -> Media c (t, (a, t)) -> Media c a
rearrangeSustain tr m = 
    case m of
        Prim a  -> Prim $ stretch (newDur tr / oldDur tr) $ fst $ snd a
        a :+: b -> (rearrangeSustainSeq dta dta' dtb dtb') (ra a) (rb b)
        a :=: b -> (rearrangeSustainPar dta dta' dtb dtb') (ra a) (rb b)
        Control c a -> Control c $ ra a
    where sf   = subForest tr
          dta  = oldDur $ sf !! 0
          dtb  = oldDur $ sf !! 1
          dta' = newDur $ sf !! 0
          dtb' = newDur $ sf !! 1
          ra   = rearrangeSustain $ sf !! 0
          rb   = rearrangeSustain $ sf !! 1

rearrangeSustainSeq :: Stretchable t a =>
    t -> t -> t -> t ->
    Media c a -> Media c a -> Media c a
rearrangeSustainSeq dta dta' dtb dtb' a b
    | dta' < dta = sequent [a, none (dta - dta'), b]
    | dta' > dta && dtab  > 0 = parallel [sequent [a, none dtab], delay dta b]
    | dta' > dta && dtab <= 0 = parallel [a, sequent [delay dta b, none (abs $ dtab)]]
    | otherwise  = sequent[a, b]
    where dtab = dta + dtb' - dta' 


rearrangeSustainPar :: Stretchable t a =>
    t -> t -> t -> t ->
    Media c a -> Media c a -> Media c a
rearrangeSustainPar dta dta' dtb dtb' a b  
    | dtab < 0  = parallel [sequent [a, none (abs dtab)], b]
    | otherwise = parallel [a, sequent [b, none (abs dtab)]]
    where dtab = dta' - dtb'



setSustainDurs :: Dur t => Media c (t, (a, t)) -> Tree (t, t)
setSustainDurs = fold prim seq par contr
    where prim  a   = Node (fst a, snd $ snd a) []
          seq   a b = Node (on (+) oldDur a b, oldDur a + newDur b) [a, b]
          par   a b = Node (on max oldDur a b, on max newDur a b)   [a, b]
          contr c a = Node (oldDur a, newDur a) [a]
         

oldDur = fst . rootLabel
newDur = snd . rootLabel

    
-- transformers

-- | 'fold' replica for MediaUnit
foldU :: Dur t => (t -> a -> b) -> (b -> b -> b) -> (b -> b -> b) -> (c -> b -> b)
	       -> MediaUnit t c a -> Maybe b
foldU prim seq par mod = fold prim' (liftA2 seq) (liftA2 par) mod' . unMediaUnit
	where prim' (Unit t a) = prim t <$> a
	      mod' c = fmap (mod c)
	     	

instance Dur t => Reversible (MediaUnit t c a) where
    reverse (MediaUnit t m) = MediaUnit t $ snd $ fold prim seq par contr m
            where prim u@(Unit dt a) = (dt, Prim u)
                  seq (da, a) (db, b) = (da + db, b :+: a)
                  par (da, a) (db, b) = (\x -> (max da db, x)) $ 
                        (if (da < db) 
                         then delay (db - da) a =:= b
                         else a =:= delay (da - db) b)
                  contr c (da, a) = (da, Control c a)
                    

instance Dur t => Sliceable t (MediaUnit t c a) where
	slice t0 t1 (MediaUnit t a) = MediaUnit (t1 - t0) $ slice t0 t1 a

-- Structure

instance Dur t => Construct (MediaUnit t c) where
	prim = MediaUnit 1 . prim . prim

instance Dur t => Arrangeable (MediaUnit t c a) where
	a +:+ b = MediaUnit (on (+) dur a b) $ on (+:+) unMediaUnit a b
	a =:= b = MediaUnit (on max dur a b) $ on (=:=) unMediaUnit a b
	

instance Dur t => Controlable c (MediaUnit t c a) where
	control c (MediaUnit t a) = MediaUnit t $ control c a

-- meaning

-- | Interpretation of 'MediaUnit'
--
-- it relies on properties of 'Unit' (it's temporal and covertible to 'Maybe')
fromMediaUnit :: Dur t => (c -> EventList t a -> EventList t a) 
	-> MediaUnit t c a -> EventList t a
fromMediaUnit f = toEventList f . unMediaUnit

------------------------------------------------------------
-- Unit

-- | unit values that can happen and lasts for some time
data Dur t => Unit t a = Unit t (Maybe a)
	deriving (Show, Eq)

instance Dur t => Functor (Unit t) where
	fmap f (Unit t a) = Unit t $ fmap f a

instance Dur t => Monad (Unit t) where
	return   = prim 
	(Unit t a) >>= f = case fmap f a of
				Nothing         -> none t
				Just (Unit t' b) -> Unit (t * t') b

instance Dur t => Applicative (Unit t) where
	pure  = return
	(<*>) = ap

-- time 

instance Dur t => Temporal t (Unit t a) where
	none t = Unit t Nothing
	dur (Unit t _) = t

instance Dur t => Stretchable t (Unit t a) where
	stretch d (Unit t a) = Unit (d * t) a

instance Dur t => ToMaybe (Unit t) where
	toMaybe (Unit _ a) = a

instance Dur t => TemporalFunctor t (Unit t) where
    dmap f (Unit t a) = Unit t $ fmap (f t) a
    
    tdmap f (Unit dt a) = 
          case a of
            Just x  -> phi dt x
            Nothing -> Unit dt Nothing
         where phi t x = Unit t $ Just $ f 0 t x

-- Unit transformers

instance Dur t => Reversible (Unit t a) where
	reverse = id

instance Dur t => Sliceable t (Unit t a) where
	slice t0 t1 u@(Unit t a) 
		| t1 < t0          = sliceErrorMessage
		| t1 < (t - eps) || t0 > eps = none $ t1 - t0 
		| otherwise        = u
		where eps = 1e-6 

-- structure

instance Dur t => Construct (Unit t) where
	prim a = Unit 1 $ Just a

-----------------------------------------------------------
-----------------------------------------------------------
-- Misc
--

-- | relative tmap
--
-- time values are normalized by argument duration.
--
-- @Dur t => t inside [0, 1]@ where 1 is total duration of second argument
tmapRel :: (TemporalFunctor t f, Temporal t (f a)) => (t -> a -> b) -> f a -> f b
tmapRel f x = tmap (f . ( / dur x)) x

-- | relative dmap
--
-- time values are normalized by argument duration.
--
-- @Dur t => t inside [0, 1]@ where 1 is total duration of second argument
dmapRel :: (TemporalFunctor t f, Temporal t (f a)) => (t -> a -> b) -> f a -> f b
dmapRel f x = dmap (f . ( / dur x)) x

-- | relative tdmap
--
-- time values are normalized by argument duration.
--
-- @Dur t => t inside [0, 1]@ where 1 is total duration of second argument
tdmapRel :: (TemporalFunctor t f, Temporal t (f a)) => (t -> t -> a -> b) -> f a -> f b
tdmapRel f x = tdmap (on f ( / dur x)) x

-- | relative tstretch
tstretchRel :: (Temporal t a, TemporalStretchable t a) => (t -> t) -> a -> a
tstretchRel f x = tstretch (f . (/ dur x)) x

-- linear interpolation

linseg1 :: (Num t, Ord t, Fractional t) => (t, t, t) -> (t -> t)
linseg1 (a, dur, b) x = a + (b - a) * x / dur

-- | linear interpolation
linseg :: (Num t, Ord t, Fractional t) => [t] -> t -> t
linseg ps x
    | x < 0     = head ps
    | otherwise = if null ds 
                  then last ps
                  else flip f x $ head ds
    where ds  = offset x ps
          f ((k, _), p) x = linseg1 p $ x - k
   
offset :: (Num t, Ord t) => t -> [t] -> [((t, t), (t, t, t))]
offset x ps = dropWhile ((x > ) . snd . fst) $ zip (stamps ps') ps'
    where ps' = parts ps

stamps :: (Num t) => [(t, t, t)] -> [(t, t)]
stamps xs = P.reverse $ foldl f [(0, p)] xs
    where f ((a, b):res) x = (b, b + snd3 x):(a, b):res
          snd3 (_, a, _) = a
          p = snd3 $ head xs

parts :: [t] -> [(t, t, t)]
parts xs = 
    case xs of
        (a:dur:b:[])      -> [(a, dur, b)] 
        (a:dur:b:(x:xs')) -> (a, dur, b) : parts (b : x : xs')
        _                 -> error "linseg : length must be odd and greater than 2"