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

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Combinators for manipulating scores and related structures.
--
-------------------------------------------------------------------------------------

module Music.Score.Combinators (
        -- * Basic
        note,
        rest,
        noteRest,
        removeRests,

        -- * Mapping over events
        mapEvents,

        -- * Filtering events

        -- ** Editing
        filterEvents,
        mapFilterEvents,

        -- * Editing
        before,
        after,
        split,
        slice,
        splice,

        -- * Meta-events
        metaAt,
        metaAtStart,
        withMeta,
        withGlobalMeta,
        withMetaAtStart,
        withGlobalMetaAtStart,




        -- ** Map over phrases
        mapFirst,
        mapLast,
        mapPhrase,
        mapPhraseSingle,

        -- * Parts
        -- ** Extracting parts
        filterPart,
        extractParts,
        extractParts',

        -- ** Map over parts
        -- mapPart,
        mapParts,
        mapAllParts,
        -- modifyParts,

        -- ** Part composition
        (</>),
        rcat,
        -- moveParts,
        -- moveToPart,

        -- * Zippers
        -- apply,
        -- snapshot,
        -- snapshotWith,

        -- ** Single-part versions
        applySingle,
        -- snapshotSingle,
        -- snapshotWithSingle,
  ) where

import           Control.Applicative
import           Control.Arrow
import           Control.Lens           hiding (perform)
import           Control.Monad
import           Control.Monad.Plus
import           Data.AffineSpace
import           Data.AffineSpace.Point
import           Data.Foldable          (Foldable (..))
import           Data.Ord
import           Data.Ratio
import           Data.Semigroup
import           Data.String
import           Data.Traversable
import           Data.VectorSpace

import           Music.Score.Convert
import           Music.Score.Meta
import           Music.Score.Note
import           Music.Score.Part
import           Music.Score.Score
import           Music.Score.Track
import           Music.Score.Util
import           Music.Score.Voice
import           Music.Time
import           Music.Time.Reactive

import qualified Data.Foldable          as Foldable
import qualified Data.List              as List

-- | Create a score containing a note at time zero and duration one. This is an alias for 'return'.
note :: Monad m => a -> m a
note = return

-- | Create a score containing a rest at time zero and duration one. This is an alias for @'return' 'Nothing'@.
rest :: MonadPlus m => m (Maybe a)
rest = return Nothing

-- | Create a note or a rest at time zero and duration one. This is an alias for 'mfromMaybe'.
noteRest :: MonadPlus m => Maybe a -> m a
noteRest = mfromMaybe

-- | Remove all rests from a score. This is an alias for 'mcatMaybes'.
removeRests :: MonadPlus m => m (Maybe a) -> m a
removeRests = mcatMaybes

-- | Retain only the notes whose /offset/ does not fall after the given time.
before :: Time -> Score a -> Score a
before u = filterEvents (\t d _ -> t .+^ d <= u)

-- | Retain only the notes whose /onset/ does not fall before the given time.
after :: Time -> Score a -> Score a
after u = filterEvents (\t d _ -> u <= t)

-- | Returns notes whose /onset/ and /offset/ fall between the given times.
slice :: Time -> Time -> Score a -> Score a
slice u v = filterEvents (\t d _ -> u <= t && t .+^ d <= v)

-- | Split a score into events whose onsets
split :: Time -> Score a -> (Score a, Score a)
split t a = (before t a, after t a)

-- | Split a score into three parts
splice :: Time -> Duration -> Score a -> (Score a, Score a, Score a)
splice t d a = tripr (before t a, split (t .+^ d) a)





-- |
-- Map over the first, and remaining notes in each part.
--
-- If a part has only one notes, the first function is applied.
-- If a part has no notes, it is returned unchanged.
--
mapFirst :: HasPart' a => (a -> b) -> (a -> b) -> Score a -> Score b
mapFirst f g = mapPhrase f g g

-- |
-- Map over the last, and preceding notes in each part.
--
-- If a part has only one notes, the first function is applied.
-- If a part has no notes, it is returned unchanged.
--
mapLast :: HasPart' a => (a -> b) -> (a -> b) -> Score a -> Score b
mapLast f g = mapPhrase g g f

-- |
-- Map over the first, middle and last note in each part.
--
-- If a part has fewer than three notes the first takes precedence over the last,
-- and last takes precedence over the middle.
--
mapPhrase :: HasPart' a => (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b
mapPhrase f g h = mapAllParts (fmap $ mapPhraseSingle f g h)

-- |
-- Equivalent to 'mapPhrase' for single-part scores.
--
-- Fails if the score contains overlapping events.
--
-- > (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b
--
mapPhraseSingle :: (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b
mapPhraseSingle f g h = mapAll (mapFTL (_3 %~ f) (_3 %~ g) (_3 %~ h))



-- |
-- Map over all events in a score.
--
mapAll :: ([(Time, Duration, a)] -> [(Time, Duration, b)]) -> Score a -> Score b
mapAll f = saveMeta $ over events f
    where
        saveMeta f x = (meta .~) ((view meta) x) $ f x



--------------------------------------------------------------------------------
-- Parts
--------------------------------------------------------------------------------

-- |
-- Filter a score to include only those events whose parts match a given predicate.
--
filterPart :: HasPart' a => (Part a -> Bool) -> Score a -> Score a
filterPart p = mfilter (p . getPart)

filterPartIs :: HasPart' a => Part a -> Score a -> Score a
filterPartIs = filterPart <$> (==)

-- |
-- Extract parts from the a score.
--
-- The parts are returned in the order defined the associated 'Ord' instance part type.
--
extractParts :: HasPart' a => Score a -> [Score a]
extractParts x = filterPartIs <$> getParts x <*> return x

-- |
-- Extract parts from the a score and include the part name.
--
-- The parts are returned in the order defined the associated 'Ord' instance part type.
--
extractParts' :: HasPart' a => Score a -> [(Part a, Score a)]
extractParts' x = getParts x `zip` extractParts x


-- |
-- Map over a specific part in the given score.
--
mapPart         :: (Enum (Part a), HasPart' a) => Part a -> (Score a -> Score a) -> Score a -> Score a

-- |
-- Map over all parts in the given score.
--
-- > (Score a -> Score a) -> Score a -> Score a
--
mapParts        :: HasPart' a => (Score a -> Score b) -> Score a -> Score b

-- |
-- Map over all parts in the given score.
--
-- > ([Score a] -> [Score a]) -> Score a -> Score a
--
mapAllParts     :: HasPart' a => ([Score a] -> [Score b]) -> Score a -> Score b

{-# DEPRECATED mapParts "" #-}
{-# DEPRECATED mapAllParts "" #-}
{-# DEPRECATED filterPart "" #-}
{-# DEPRECATED extractParts "" #-}
{-# DEPRECATED extractParts' "" #-}


mapPart n f     = mapAllParts (zipWith ($) (replicate (fromEnum n) id ++ [f] ++ repeat id))
mapParts f      = mapAllParts (fmap f)
mapAllParts f   = mconcat . f . extractParts

-- |
-- Modify all parts in the given score.
--
-- > (Part -> Part) -> Score a -> Score a
--
modifyParts :: HasPart' a => (Part a -> Part a) -> Score a -> Score a
modifyParts n = fmap (modifyPart n)



--------------------------------------------------------------------------------
-- Part composition
--------------------------------------------------------------------------------

infixr 6 </>

-- |
-- Similar to '<>', but increases parts in the second part to prevent collision.
--
(</>) :: (HasPart' a, Enum (Part a)) => Score a -> Score a -> Score a
a </> b = a <> moveParts offset b
    where
        -- max voice in a + 1
        offset = succ $ maximum' 0 $ fmap fromEnum $ getParts a

-- |
-- Concatenate parts.
--
rcat :: (HasPart' a, Enum (Part a)) => [Score a] -> Score a
rcat = List.foldr (</>) mempty

-- |
-- Move down one voice (all parts).
--
moveParts :: (Integral b, HasPart' a, Enum (Part a)) => b -> Score a -> Score a
moveParts x = modifyParts (successor x)

-- |
-- Move top-part to the specific voice (other parts follow).
--
moveToPart :: (Enum b, HasPart' a, Enum (Part a)) => b -> Score a -> Score a
moveToPart v = moveParts (fromEnum v)



-------------------------------------------------------------------------------------
-- Zippers

-- |
-- Apply a time-varying function to all events in score.
--
apply :: HasPart' a => Voice (Score a -> Score b) -> Score a -> Score b
apply x = mapAllParts (fmap $ applySingle x)

-- |
-- Apply a time-varying function to all events in score.
--
applySingle :: Voice (Score a -> Score b) -> Score a -> Score b
applySingle fs = notJoin . fmap (uncurry ($)) . sample fs
    where
        notJoin   = mconcat . Foldable.toList
        sample fs = snapshotSingle (voiceToScore fs)
{-# DEPRECATED applySingle "" #-}

-- |
-- Get all notes that start during a given note.
--
snapshot :: HasPart' b => Score a -> Score b -> Score (a, Score b)
snapshot x = mapAllParts (fmap $ snapshotSingle x)

snapshotWith :: HasPart' b => (a -> Score b -> c) -> Score a -> Score b -> Score c
snapshotWith f x = mapAllParts (fmap $ snapshotWithSingle f x)

-- |
-- Get all notes that start during a given note.
--
snapshotSingle :: Score a -> Score b -> Score (a, Score b)
snapshotSingle = snapshotWithSingle (,)

snapshotWithSingle :: (a -> Score b -> c) -> Score a -> Score b -> Score c
snapshotWithSingle g as bs = mapEvents ( \t d a -> g a (onsetIn t d bs) ) as

-- |
-- Filter out events that has its onset in the given time interval (inclusive start).
-- For example, onset in 1 2 filters events such that (1 <= onset x < 3)
--
onsetIn :: Time -> Duration -> Score a -> Score a
onsetIn a b = mapAll $ filterOnce (\(t,d,x) -> a <= t && t < a .+^ b)
-- We could also have used mfilter. filterOnce is more lazy,
-- but depends on the events being sorted







withSpan :: Score a -> Score (Span, a)
withSpan = mapEvents (\t d x -> (t >-> d,x))
withTime = mapEvents (\t d x -> (t, x))

inSpan t' (view range -> (t,u)) = t <= t' && t' < u

-- TODO clean
mapBefore :: Time -> (Score a -> Score a) -> Score a -> Score a
mapDuring :: Span -> (Score a -> Score a) -> Score a -> Score a
mapAfter :: Time -> (Score a -> Score a) -> Score a -> Score a
mapBefore t f x = let (y,n) = (fmap snd *** fmap snd) $ mpartition (\(t2,x) -> t2 < t) (withTime x) in (f y <> n)
mapDuring s f x = let (y,n) = (fmap snd *** fmap snd) $ mpartition (\(t,x) -> t `inSpan` s) (withTime x) in (f y <> n)
mapAfter t f x = let (y,n) = (fmap snd *** fmap snd) $ mpartition (\(t2,x) -> t2 >= t) (withTime x) in (f y <> n)


-- Transform the score with the current value of some meta-information
-- Each "update chunk" of the meta-info is processed separately

runScoreMeta :: forall a b . (HasPart' a, IsAttribute b) => Score a -> Reactive b
runScoreMeta = runMeta (Nothing :: Maybe a) . (view meta)

metaAt :: (HasPart' a, IsAttribute b) => Time -> Score a -> b
metaAt x = (? x) . runScoreMeta

metaAtStart :: (HasPart' a, IsAttribute b) => Score a -> b
metaAtStart x = onset x `metaAt` x

withGlobalMeta :: IsAttribute a => (a -> Score b -> Score b) -> Score b -> Score b
withGlobalMeta = withMeta' (Nothing :: Maybe Int)

withMeta :: (IsAttribute a, HasPart' b) => (a -> Score b -> Score b) -> Score b -> Score b
withMeta f x = withMeta' (Just x) f x

withMeta' :: (HasPart' c, IsAttribute a) => Maybe c -> (a -> Score b -> Score b) -> Score b -> Score b
withMeta' part f x = let
    m = (view meta) x
    r = runMeta part m
    in case splitReactive r of
        Left  a -> f a x
        Right ((a, t), bs, (u, c)) ->
            (meta .~) m
                $ mapBefore t (f a)
                $ (composed $ fmap (\(getNote -> (s, a)) -> mapDuring s $ f a) $ bs)
                $ mapAfter u (f c)
                $ x

withGlobalMetaAtStart :: IsAttribute a => (a -> Score b -> Score b) -> Score b -> Score b
withGlobalMetaAtStart = withMetaAtStart' (Nothing :: Maybe Int)

withMetaAtStart :: (IsAttribute a, HasPart' b) => (a -> Score b -> Score b) -> Score b -> Score b
withMetaAtStart f x = withMetaAtStart' (Just x) f x

withMetaAtStart' :: (IsAttribute b, HasPart' p) =>
    Maybe p -> (b -> Score a -> Score a) -> Score a -> Score a
withMetaAtStart' part f x = let
    m = (view meta) x
    in f (runMeta part m ? onset x) x





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


-- partial2 :: (a -> b -> Bool)      -> a -> b -> Maybe b
-- partial3 :: (a -> b -> c -> Bool) -> a -> b -> c -> Maybe c
-- partial2 f = curry  (fmap snd  . partial (uncurry f))
-- partial3 f = curry3 (fmap (^. _3) . partial (uncurry3 f))

iterating :: (a -> a) -> (a -> a) -> Int -> a -> a
iterating f g n
    | n <  0 = f . iterating f g (n + 1)
    | n == 0 = id
    | n >  0 = g . iterating f g (n - 1)

successor :: (Integral b, Enum a) => b -> a -> a
successor n = iterating pred succ (fromIntegral n)

maximum' :: (Ord a, Foldable t) => a -> t a -> a
maximum' z = option z getMax . foldMap (Option . Just . Max)

minimum' :: (Ord a, Foldable t) => a -> t a -> a
minimum' z = option z getMin . foldMap (Option . Just . Min)