{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE DeriveTraversable          #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeFamilies               #-}

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


module Music.Score.Meta.Annotations (
        Annotation,
        annotate,
        annotateSpan,
        showAnnotations,
        showAnnotations',
        withAnnotations,
  ) where

import           Control.Arrow
import           Control.Monad.Plus
import qualified Data.List
import           Data.Semigroup
import           Data.String
import           Data.Typeable
import           Data.Void

import           Music.Score.Combinators (withGlobalMeta)
import           Music.Score.Meta
import           Music.Score.Note
import           Music.Score.Ornaments   (HasText, text)
import           Music.Score.Part
import           Music.Score.Score
import           Music.Time
import           Music.Time.Reactive

-- |
--   An annotation is a unique textual value attached to parts of a score.
--   They are ignored by default, but can be collected with 'withAnnotations'.
--
newtype Annotation = Annotation { getAnnotation_ :: [String] }
    deriving (Semigroup, Monoid, Typeable)
instance IsString Annotation where fromString = Annotation . return

getAnnotation :: Annotation -> [String]
getAnnotation = Data.List.nub . getAnnotation_

-- | Annotate the whole score.
annotate :: String -> Score a -> Score a
annotate str x = annotateSpan (start >-> duration x) str x

-- | Annotate a part of the score.
annotateSpan :: Span -> String -> Score a -> Score a
annotateSpan span str x = addGlobalMetaNote (sapp span $ return $ Annotation [str]) x

-- | Show all annotations in the score.
showAnnotations :: (HasPart' a, HasText a) => Score a -> Score a
showAnnotations = showAnnotations' ":"

-- | Show all annotations in the score using the given prefix.
showAnnotations' :: (HasPart' a, HasText a) => String -> Score a -> Score a
showAnnotations' prefix = withAnnotations (flip $ \s -> foldr (text . (prefix ++ )) s)

-- | Handle the annotations in a score.
withAnnotations :: (HasPart' a, HasText a) => ([String] -> Score a -> Score a) -> Score a -> Score a
withAnnotations f = withGlobalMeta (f . getAnnotation)