{-|
Module: Time
Description: Defines core data types and functions for handling tidal's concept of time in `Arc`s & `Event`s
-}
module Sound.Tidal.Time where

import Sound.Tidal.Utils
import Data.Ratio

-- | Time is represented by a rational number. Each natural number
-- represents both the start of the next rhythmic cycle, and the end
-- of the previous one. Rational numbers are used so that subdivisions
-- of each cycle can be accurately represented.
type Time = Rational

-- | @(s,e) :: Arc@ represents a time interval with a start and end value.
-- @ { t : s <= t && t < e } @
type Arc = (Time, Time)

-- | An Event is a value that occurs during the period given by the
-- first @Arc@. The second one indicates the event's "domain of
-- influence". These will often be the same, but many temporal
-- transformations, such as rotation and scaling time, may result in
-- arcs being split or truncated. In such cases, the first arc is
-- preserved, but the second arc reflects the portion of the event
-- which is relevant.
type Event a = (Arc, Arc, a)

-- | The starting point of the current cycle. A cycle occurs from each
-- natural number to the next, so this is equivalent to @floor@.
sam :: Time -> Time
sam = fromIntegral . floor

-- | The end point of the current cycle (and starting point of the next cycle)
nextSam :: Time -> Time
nextSam = (1+) . sam


-- | The position of a time value relative to the start of its cycle.
cyclePos :: Time -> Time
cyclePos t = t - sam t

-- | @isIn a t@ is @True@ if @t@ is inside
-- the arc represented by @a@.
isIn :: Arc -> Time -> Bool
isIn (s,e) t = t >= s && t < e

-- | Splits the given @Arc@ into a list of @Arc@s, at cycle boundaries.
arcCycles :: Arc -> [Arc]
arcCycles (s,e) | s >= e = []
                | sam s == sam e = [(s,e)]
                | otherwise = (s, nextSam s) : (arcCycles (nextSam s, e))

-- | Splits the given @Arc@ into a list of @Arc@s, at cycle boundaries, but wrapping the arcs within the same cycle.
arcCycles' :: Arc -> [Arc]
arcCycles' (s,e) | s >= e = []
                 | sam s == sam e = [(s,e)]
                 | otherwise = (s, nextSam s) : (arcCycles' ((nextSam s) - 1, e - 1))


-- | @subArc i j@ is the arc that is the intersection of @i@ and @j@.
subArc :: Arc -> Arc -> Maybe Arc
subArc (s, e) (s',e') | s'' < e'' = Just (s'', e'')
                      | otherwise = Nothing
  where s'' = max s s'
        e'' = min e e'

-- | Map the given function over both the start and end @Time@ values
-- of the given @Arc@.
mapArc :: (Time -> Time) -> Arc -> Arc
mapArc f (s,e) = (f s, f e)

-- | Similar to @mapArc@ but time is relative to the cycle (i.e. the
-- sam of the start of the arc)
mapCycle :: (Time -> Time) -> Arc -> Arc
mapCycle f (s,e) = (sam' + (f $ s - sam'), sam' + (f $ e - sam'))
         where sam' = sam s

-- | Returns the `mirror image' of an @Arc@, used by @Sound.Tidal.Pattern.rev@.
mirrorArc :: Arc -> Arc
mirrorArc (s, e) = (sam s + (nextSam s - e), nextSam s - (s - sam s))

-- | The start time of the given @Event@
eventStart :: Event a -> Time
eventStart = fst . snd'

-- | The original onset of the given @Event@
eventOnset :: Event a -> Time
eventOnset = fst . fst'

-- | The original offset of the given @Event@
eventOffset :: Event a -> Time
eventOffset = snd . fst'

-- | The arc of the given @Event@
eventArc :: Event a -> Arc
eventArc = snd'

-- | The midpoint of an @Arc@
midPoint :: Arc -> Time
midPoint (s,e) = s + ((e - s) / 2)

-- | `True` if an `Event`'s first and second `Arc`'s start times match
hasOnset :: Event a -> Bool
hasOnset ((s,_), (s',_), _) = s == s'

-- | `True` if an `Event`'s first and second `Arc`'s end times match
hasOffset :: Event a -> Bool
hasOffset ((_,e), (_,e'), _) = e == e'

-- | `True` if an `Event`'s starts is within given `Arc`
onsetIn :: Arc -> Event a -> Bool
onsetIn a e = isIn a (eventOnset e)

-- | `True` if an `Event`'s ends is within given `Arc`
offsetIn :: Arc -> Event a -> Bool
offsetIn a e = isIn a (eventOffset e)