{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE DeriveTraversable          #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE TypeFamilies               #-}

module Music.Score.Note (
        Note,
        getNote,
        getNoteSpan,
        getNoteValue,
        (=:),
  ) where

import           Control.Applicative
import           Control.Comonad
import           Control.Comonad.Env
import           Control.Monad

import           Data.Foldable          (Foldable)
import qualified Data.Foldable          as F
import           Data.PairMonad         ()
import           Data.Traversable       (Traversable)
import qualified Data.Traversable       as T

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

newtype Note a = Note { getNote_ :: (Span, a) }
    deriving (Eq, Ord, Show, {-Read, -}Functor, Applicative, Monad, Comonad, Foldable, Traversable)

-- |
-- Deconstruct a note.
--
-- Typically used with the @ViewPatterns@ extension, as in
--
-- > foo (getNote -> (s,x)) = ...
--
getNote :: Note a -> (Span, a)
getNote (Note x) = x

-- | Get the span of the note. Same as 'era' and 'ask'.
getNoteSpan :: Note a -> Span
getNoteSpan = fst . getNote

-- | Get the value of the note. Same as 'extract'.
getNoteValue :: Note a -> a
getNoteValue = snd . getNote

-- Note that
-- extract = getNoteValue
-- ask = getNoteSpan

instance ComonadEnv Span Note where
    ask = getNoteSpan

instance Delayable (Note a) where
    delay n (Note (s,x)) = Note (delay n s, x)

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

instance HasOnset (Note a) where
    onset (Note (s,x)) = onset s

instance HasOffset (Note a) where
    offset (Note (s,x)) = offset s

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

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

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

type instance Pitch (Note a) = Pitch a
instance HasGetPitch a => HasGetPitch (Note a) where
    __getPitch  = __getPitch . getNoteValue
instance HasSetPitch a b => HasSetPitch (Note a) (Note b) where
    type SetPitch g (Note a) = Note (SetPitch g a)
    __mapPitch f = fmap (__mapPitch f)


-- | Construct a note from a span and value.
--
-- Typically used with the span constructors as in:
--
-- > 0 <-> 2 =: c
-- > 0 >-> 1 =: d
--
(=:) :: Span -> a -> Note a
s =: x  =  Note (s,x)