{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies               #-}

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (TF,GNTD)
--
-------------------------------------------------------------------------------------

module Music.Time.Juxtapose (
        -- * Prerequisites
        Transformable(..),

        -- ** Juxtaposing values
        following,
        preceding,
        during,

        -- * Composing values
        (|>),
        (>|),
        (<|),
        scat,
        pcat,

        -- ** Special composition
        sustain,
        anticipate,

        -- ** Repetition
        times,
        repeated,
        group,
  ) where


import           Data.AffineSpace
import           Data.AffineSpace.Point
import           Data.Monoid.WithSemigroup
import           Data.Semigroup
import           Data.VectorSpace

import           Music.Time.Delayable
import           Music.Time.Onset
import           Music.Time.Stretchable
import           Music.Time.Time

-- |
-- This pseudo-class gathers the restrictions needed to implement position a value at
-- any point and duration in time.
--
type Transformable a   =  (Stretchable a, Delayable a)


-------------------------------------------------------------------------------------
-- Juxtaposition
-------------------------------------------------------------------------------------

-- |
-- @a \`following\` b@ moves score /b/ so that its onset is at the offset of score
-- /a/ and returns the moved score.
--
following :: (HasOffset a, Delayable b, HasOnset b) => a -> b -> b
a `following` b =  startAt (offset a) b

-- |
-- @a \`preceding\` b@ moves score /a/ so that its offset is at the onset of score
-- /b/ and returns the moved score.
--
preceding :: (Delayable a, HasOffset a, HasOnset b) => a -> b -> a
a `preceding` b =  stopAt (onset b) a

-- | @a \`during\` b@ places /a/ at the same era as /b/ and returns the moved score.
during :: (Delayable a, Stretchable a, HasOnset a, HasDuration a, HasOnset b, HasDuration b) => a -> b -> a
a `during` b = startAt (onset b) $ stretchTo (duration b) a

-------------------------------------------------------------------------------------
-- Composition
-------------------------------------------------------------------------------------

infixr 6 |>
infixr 6 >|
infixr 6 <|

-- |
-- Compose in sequence.
--
-- @a |> b@ moves score /b/ as per 'following' and then composes the resulting scores with '<>'.
--
-- > Score a -> Score a -> Score a
--
(|>)            :: (Semigroup a, HasOnset a, HasOffset a, Delayable a) =>
                a -> a -> a
-- |
-- Compose in sequence.
--
-- @a >| b@ moves score /a/ as per 'preceding' and then composes the resulting scores with '<>'.
--
-- > Score a -> Score a -> Score a
--
(>|)            :: (Semigroup a, HasOnset a, HasOffset a, Delayable a) =>
                a -> a -> a

-- |
-- Compose in reverse sequence.
--
-- To compose in parallel, use '<>'.
--
-- > Score a -> Score a -> Score a
--
(<|)            :: (Semigroup a, HasOnset a, HasOffset a, Delayable a) =>
                a -> a -> a

a |> b =  a <> (a `following` b)
a >| b =  (a `preceding` b) <> b
a <| b =  b |> a

-- |
-- Sequential catenation.
--
-- > [Score a] -> Score a
--
scat            :: (Monoid' a, HasOnset a, HasOffset a, Delayable a) =>
                [a] -> a
-- |
-- Parallel catenation.
--
-- > [Score a] -> Score a
--
pcat            :: Monoid' a =>
                [a] -> a

scat = Prelude.foldr (|>) mempty
pcat = Prelude.foldr (<>) mempty


-- |
-- Like '<>', but scaling the second agument to the duration of the first.
--
-- > Score a -> Score a -> Score a
--
sustain         :: (Semigroup a, Stretchable a, HasDuration a, Fractional d, d ~ Duration) =>
                a -> a -> a

-- |
-- Like '|>' but with a negative delay on the second element.
--
-- > Duration -> Score a -> Score a -> Score a
--
anticipate      :: (Semigroup a, Transformable a, HasOnset a, HasOffset a, Ord d, d ~ Duration) =>
                d -> a -> a -> a

x `sustain` y     = x <> duration x `stretchTo` y
anticipate t a b  =  a <> startAt (offset a .-^ t) b

-- Like '<>', but truncating the second agument to the duration of the first.
-- prolong x y = x <> before (duration x) y


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

-- |
-- Repeat exact amount of times.
--
-- > Duration -> Score Note -> Score Note
--
times           :: (Monoid' a, Transformable a, HasOnset a, HasOffset a) =>
                Int -> a -> a

-- |
-- Repeat once for each element in the list.
--
-- Example:
--
-- > repeated [1,2,1] (c^*)
--
-- Simple type:
--
-- > [a] -> (a -> Score Note) -> Score Note
--
repeated        :: (Monoid' b, Transformable b, HasOnset b, HasOffset b) =>
                [a] -> (a -> b) -> b

-- |
-- Repeat a number of times and scale down by the same amount.
--
-- > Duration -> Score a -> Score a
--
group           :: (Monoid' a, Transformable a, Fractional d, d ~ Duration, HasOnset a, HasOffset a) =>
                Int -> a -> a

times n     = scat . replicate n
repeated    = flip (\f -> scat . fmap f)
group n     = times n . (fromIntegral n `compress`)