{-# LANGUAGE
    TypeFamilies,
    DeriveFunctor,
    DeriveFoldable,
    DeriveDataTypeable,
    DeriveTraversable,
    GeneralizedNewtypeDeriving #-}

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

module Music.Score.Score (
        Score,
        -- mapTime,
  ) where

import Prelude hiding (null, length, repeat, foldr, concat, foldl, mapM, concatMap, maximum, sum, minimum)

import Data.Semigroup
import Control.Applicative
import Control.Monad (ap, join, MonadPlus(..))
import Data.Foldable
import Data.Traversable
import Data.Typeable
import Data.Maybe
import Data.Either
import Data.Pointed
import Data.Function (on)
import Data.Ord (comparing)
import Data.Ratio
import Data.VectorSpace
import Data.AffineSpace
import Test.QuickCheck (Arbitrary(..),Gen(..))
import qualified Data.Map as Map
import qualified Data.List as List

import Music.Pitch.Literal
import Music.Dynamics.Literal

import Music.Time
import Music.Score.Voice
import Music.Score.Track

-------------------------------------------------------------------------------------
-- Score type
-------------------------------------------------------------------------------------

-- |
-- A score is a list of events, i.e. time-duration-value triplets. Semantically
--
-- > type Score a = [(Time, Duration, a)]
--
-- There is no explicit representation for rests. However you can use `Score (Maybe a)` to
-- represent a score with rests. Such rests are only useful when composing scores. They
-- may be removed with 'removeRests'.
--
-- Score is a 'Monoid' under parallel composition. 'mempty' is a score of no parts.
-- For sequential composition of scores, use '|>'.
--
-- Score has an 'Applicative' instance derived from the 'Monad' instance. Not sure it is useful.
--
-- Score is an instance of 'VectorSpace' using sequential composition as addition,
-- and time scaling as scalar multiplication.
--
newtype Score a  = Score { getScore :: [(TimeT, DurationT, a)] }
    deriving (Eq, Ord, Show, Functor, Foldable, Typeable, Traversable)

type instance Time Score = TimeT

instance Semigroup (Score a) where
    (<>) = mappend

-- Equivalent to the derived Monoid, except for the sorted invariant.
instance Monoid (Score a) where
    mempty = Score []
    Score as `mappend` Score bs = Score (as `m` bs)
        where
            m = mergeBy (comparing fst3)

-- |
-- This instance is somewhat similar to the list instance.
--
-- * 'return' creates a score containing a single note at /(0, 1)/.
-- 
-- * @s@ '>>=' @k@ maps each note to a new score, which is then scaled and delayed by the onset and
--   duration of the original note. That is, @k@ returns a score @t@ such that /0 < onset t < offset t < 1/, 
--   the resulting events will not cross the boundaries of the original note.
-- 
-- * 'join' scales and offsets each inner score to fit into the note containing it, then
--   removes the intermediate structure.
--
-- > let s = compose [(0,1,0), (1,2,1)]
-- >
-- > s >>= \x -> compose [ (0,1,toEnum $ x+65),
-- >                       (1,3,toEnum $ x+97) ] :: Score Char
-- >
-- >     ===> compose [ (1, 1, 'A'),
-- >                    (1, 3, 'a'),
-- >                    (1, 2, 'B'),
-- >                    (3, 6, 'b') ]}
-- 
instance Monad Score where
    return x = Score [(0, 1, x)]
    a >>= k = join' $ fmap k a
        where
            join' sc = {-mconcat $ toList-}fold $ mapTime (\t d -> delay' t . stretch d) sc

instance Pointed Score where
    point = return

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

instance Alternative Score where
    empty = mempty
    (<|>) = mappend

-- Satisfies left distribution
instance MonadPlus Score where
    mzero = mempty
    mplus = mappend

instance Performable Score where
    perform = getScore

instance Stretchable (Score) where
    d `stretch` Score sc = Score $ fmap (first3 (^* fromDurationT d) . second3 (^* d)) $ sc

instance Delayable (Score) where
    d `delay` Score sc = Score . fmap (first3 (.+^ d)) $ sc

instance HasOnset (Score) where
    -- onset  (Score []) = 0
    -- onset  (Score xs) = minimum (fmap on xs)  where on  (t,d,x) = t

    -- Note: this version of onset is lazier, but depends on the invariant that the list is sorted
    onset  (Score []) = 0
    onset  (Score xs) = on (head xs) where on (t,d,x) = t

instance HasOffset (Score) where
    offset (Score []) = 0
    offset (Score xs) = maximum (fmap off xs) where off (t,d,x) = t + (fromDurationT $ d)

instance HasDuration (Score) where
    duration x = offset x .-. onset x

instance IsPitch a => IsPitch (Score a) where
    fromPitch = pure . fromPitch

instance IsDynamics a => IsDynamics (Score a) where
    fromDynamics = pure . fromDynamics

-- Utility
instance AdditiveGroup (Score a) where
    zeroV   = error "Not impl"
    (^+^)   = error "Not impl"
    negateV = error "Not impl"

instance VectorSpace (Score a) where
    type Scalar (Score a) = DurationT
    d *^ s = d `stretch` s

instance Arbitrary a => Arbitrary (Score a) where
    arbitrary = do
        x <- arbitrary
        t <- fmap toDurationT $ (arbitrary::Gen Double)
        d <- fmap toDurationT $ (arbitrary::Gen Double)
        return $ delay t $ stretch d $ (note x)

-- |
-- Create a score of duration one with the given value (same as 'return').
--
note :: a -> Score a
note = return

-- |
-- Create a score of duration one with no values.
--
rest :: Score (Maybe a)
rest = return Nothing

-- |
-- Repeat a score indefinately.
--
repeat :: Score a -> Score a
repeat a = a `plus` delay (duration a) (repeat a)
    where
        Score as `plus` Score bs = Score (as <> bs)

-- |
-- Map over all events in a score.
--
mapTime :: (TimeT -> DurationT -> a -> b) -> Score a -> Score b
mapTime f = Score . fmap (mapEvent f) . getScore

mapEvent :: (TimeT -> DurationT -> a -> b) -> (TimeT, DurationT, a) -> (TimeT, DurationT, b)
mapEvent f (t, d, x) = (t, d, f t d x)


-------------------------------------------------------------------------------------

delay' t = delay (fromTimeT t)

fst3 (a,b,c) = a

list z f [] = z
list z f xs = f xs

first f (x,y)  = (f x, y)
second f (x,y) = (x, f y)

first3 f (a,b,c) = (f a,b,c)
second3 f (a,b,c) = (a,f b,c)

mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]
mergeBy f [] ys = ys
mergeBy f xs [] = xs
mergeBy f xs'@(x:xs) ys'@(y:ys)
    | x `f` y == LT   =   x : mergeBy f xs ys'
    | x `f` y /= LT   =   y : mergeBy f xs' ys