{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE DeriveTraversable          #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
{-# LANGUAGE UndecidableInstances       #-}

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

module Music.Score.Voice (
        -- * Voice type
        Voice,
        voice',
        voice,

        zipVoice,
        zipVoiceWith,
        dzipVoiceWith,
        mergeEqual,
  ) where

import           Control.Applicative
import           Control.Arrow
import           Control.Lens
import           Control.Monad
import           Control.Monad.Compose
import           Data.Semigroup

import           Data.Foldable          (Foldable (..), foldMap)
import qualified Data.Foldable          as F
import qualified Data.List              as List
import           Data.PairMonad         ()
import           Data.Traversable       (Traversable (..))
import qualified Data.Traversable       as T
import           Data.Typeable
import           Data.VectorSpace       hiding (Sum)

import           Music.Dynamics.Literal
import           Music.Pitch.Literal
import           Music.Score.Pitch
import           Music.Score.Util
import           Music.Time


-- |
-- A voice is a list of events with explicit duration. Events can not overlap.
--
-- Voice is a 'Monoid' under sequential composition. 'mempty' is the empty part and 'mappend'
-- appends parts.
--
-- Voice is a 'Monad'. 'return' creates a part containing a single value of duration
-- one, and '>>=' transforms the values of a part, allowing the addition and
-- removal of values under relative duration. Perhaps more intuitively, 'join' scales
-- each inner part to the duration of the outer part, then removes the
-- intermediate structure.
--
-- > let p = Voice [(1, Just 0), (2, Just 1)] :: Voice Int
-- >
-- > p >>= \x -> Voice [ (1, Just $ toEnum $ x+65),
-- >                    (3, Just $ toEnum $ x+97) ] :: Voice Char
-- >
-- >     ===> Voice {getVoice = [ (1 % 1,Just 'A'),
-- >                            (3 % 1,Just 'a'),
-- >                            (2 % 1,Just 'B'),
-- >                            (6 % 1,Just 'b') ]}
--
-- Voice is a 'VectorSpace' using sequential composition as addition, and time scaling
-- as scalar multiplication.
--
newtype Voice a = Voice { getVoice' :: [Ev a] }
    deriving (Eq, Ord, Show, Functor, Foldable, Monoid, Semigroup, Typeable, Traversable, Stretchable)


instance Wrapped (Voice a) where
    type Unwrapped (Voice a) = [Ev a]
    _Wrapped' = iso getVoice' Voice

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

instance Monad Voice where
    return = (^. _Unwrapped') . return . return
    xs >>= f = (^. _Unwrapped') $ ((^. _Wrapped') . f) `mbind` ((^. _Wrapped') xs)

instance HasDuration (Voice a) where
    duration = sum . fmap duration . getVoice'

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

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

instance IsInterval a => IsInterval (Voice a) where
    fromInterval = pure . fromInterval

-- TODO
instance Num a => Num (Voice a) where
    fromInteger = pure . fromInteger

type instance Pitch (Voice a) = Pitch a
instance (HasSetPitch a b, Transformable (Pitch a), Transformable (Pitch b)) => HasSetPitch (Voice a) (Voice b) where
    type SetPitch g (Voice a) = Voice (SetPitch g a)
    -- FIXME this is wrong, need to behave like __mapPitch'
    __mapPitch f   = fmap (__mapPitch f)

-- |
-- Create a voice from a list of events.
--
voice' :: Iso' [(Duration, a)] (Voice a)
voice' = voice

-- |
-- Create a voice from a list of events.
--
voice :: Iso [(Duration, a)] [(Duration, b)] (Voice a) (Voice b)
voice = iso mkVoice getVoice
    where
        mkVoice = Voice . fmap (uncurry ev . first realToFrac)
        getVoice = fmap (first realToFrac . getEv) . getVoice'

-- |
-- Join the given voices by multiplying durations and pairing values.
--
zipVoice :: Voice a -> Voice b -> Voice (a, b)
zipVoice = zipVoiceWith (,)

-- |
-- Join the given voices by multiplying durations and combining values using the given function.
--
zipVoiceWith :: (a -> b -> c) -> Voice a -> Voice b -> Voice c
zipVoiceWith f (Voice a) (Voice b) = Voice $ zipWith (\(Ev (dx,vx)) (Ev (dy,vy)) -> Ev (dx <> dy, f vx vy)) a b

-- |
-- Join the given voices by combining durations and values using the given function.
--
dzipVoiceWith :: (Duration -> Duration -> a -> b -> (Duration, c)) -> Voice a -> Voice b -> Voice c
dzipVoiceWith f (Voice a) (Voice b) = Voice $ zipWith (\(Ev (Product dx,vx)) (Ev (Product dy,vy)) -> Ev (first Product $ f dx dy vx vy)) a b

-- |
-- Merge consecutive equal note.
--
mergeEqual :: Eq a => Voice a -> Voice a
mergeEqual = over (from voice) $ fmap f . List.groupBy (inspecting snd)
    where
        f dsAs = let (ds,as) = unzip dsAs in (sum ds, head as)

inspecting :: Eq a => (b -> a) -> b -> b -> Bool
inspecting p x y = p x == p y

newtype Ev a = Ev (Product Duration, a)
    deriving (Eq, Ord, Show, {-Read, -}Functor, Applicative, Monad, Foldable, Traversable)

ev t x = Ev (Product t, x)
getEv (Ev (Product t, x)) = (t, x)

instance Stretchable (Ev a) where
    stretch n (Ev (s,x)) = Ev (stretch n s, x)

instance HasDuration (Ev a) where
    duration (Ev (s,x)) = duration s