{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DeriveDataTypeable         #-}
{-# LANGUAGE DeriveFoldable             #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE FunctionalDependencies     #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012-2014
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (TF,GNTD)
--
-- This module provides phrase-wise traversal.
--
-------------------------------------------------------------------------------------

module Music.Score.Phrases (
    -- * HasPhrases class
    HasPhrases(..),
    HasPhrases',
    phrases,
    phrases',

    -- * Phrase types etc
    Phrase,
    MVoice,
    PVoice,
    TVoice,

    -- ** Utility
    mVoicePVoice,
    mVoiceTVoice,
    pVoiceTVoice,
    unsafeMVoicePVoice,
    singleMVoice,
    mapPhrasesWithPrevAndCurrentOnset,
  ) where

import           Control.Applicative
import           Control.Applicative
import           Control.Comonad            (Comonad (..), extract)
import           Control.Exception          (assert)
import           Control.Lens
import           Control.Lens               hiding (rewrite)
import           Control.Monad
import           Control.Monad.Plus
import           Data.AffineSpace
import           Data.AffineSpace
import           Data.Bifunctor
import           Data.Colour.Names          as Color
import           Data.Either
import           Data.Either
import           Data.Foldable              (Foldable)
import           Data.Functor.Adjunction    (unzipR)
import           Data.Functor.Context
import           Data.Functor.Contravariant (Op(..))
import           Data.Functor.Couple
import qualified Data.List                  as List
import qualified Data.List
import           Data.Maybe
import           Data.Maybe
import           Data.Ord
import           Data.Ratio
import           Data.Semigroup
import           Data.Semigroup
import           Data.Traversable
import           Data.Traversable           (Traversable, sequenceA)
import           Data.VectorSpace           hiding (Sum (..))
import           System.Process

import           Music.Score.Part
import           Music.Time
import           Music.Time.Internal.Convert
import           Music.Time.Internal.Util


-- |
-- For a phrase, we simply use a voice without rests.
--
-- To represent a sequence of phrases we provide two equivalent representations:
--
-- * 'MVoice' is a sequence of notes/chords or rests. All consecutive non-rests consitute a phrase.
--
-- * 'PVoice' is a sequence of phrases or durations.
--
type Phrase a = Voice a

-- |
-- A sequence of phrases or rests, represented as notes or rests.
--
-- Each consecutive sequence of non-rest elements is considered to be a phrase.
-- For a more explicit representation of the phrase structure, see 'PVoice'.
--
type MVoice a = Voice (Maybe a)

-- |
-- A sequence of phrases or rests, represented with explicit phrase structure.
--
type PVoice a = [Either Duration (Phrase a)]

-- |
-- A sequence of phrases or rests, represented as phrases with an explicit onset.
--
-- This is only isomorphic to 'MVoice' (and 'PVoice') up to onset equivalence.
--
type TVoice a = Track (Phrase a)


-- |
-- Classes that provide a phrase traversal.
--
class HasPhrases s t a b | s -> a, t -> b, s b -> t, t a -> s where
  mvoices :: Traversal s t (MVoice a) (MVoice b)

-- | Traverses all phrases in a voice.
instance HasPhrases (MVoice a) (MVoice b) a b where
  mvoices = id

  -- | Traverses all phrases in a voice.
instance HasPhrases (PVoice a) (PVoice b) a b where
  -- Note: This is actually OK in 'phr', as that just becomes (id . each . _Right)
  mvoices = from unsafeMVoicePVoice

-- | Traverses all phrases in each voice, using 'extracted'.
instance (HasPart' a, {-HasPart a b, -}{-Transformable a,-} Ord (Part a)) =>
  HasPhrases (Score a) (Score b) a b where
  mvoices = extracted . each . singleMVoice

type HasPhrases' s a = HasPhrases s s a a

{-
Phrase traversal for score:

phrasesS :: (Ord (Part a), HasPart' a, Transformable a) => Traversal' (Score a) (Phrase a)
phrasesS = extracted . each . singleMVoice . mVoicePVoice . each . _Right

More generally:
-}

-- |
-- A simple generic phrase-traversal.
--
phrases' :: HasPhrases' s a => Traversal' s (Phrase a)
phrases' = phrases

-- |
-- A generic phrase-traversal.
--
phrases :: HasPhrases s t a b => Traversal s t (Phrase a) (Phrase b)
phrases = mvoices . mVoicePVoice . each . _Right

-- |
-- View an 'MVoice' as a 'PVoice'.
--
mVoicePVoice :: Lens (MVoice a) (MVoice b) (PVoice a) (PVoice b)
mVoicePVoice = unsafeMVoicePVoice
-- TODO meta

-- |
-- View an 'MVoice' as a 'PVoice' and vice versa.
--
-- This a valid 'Iso' up to meta-data equivalence.
--
unsafeMVoicePVoice :: Iso (MVoice a) (MVoice b) (PVoice a) (PVoice b)
unsafeMVoicePVoice = iso mvoiceToPVoice pVoiceToMVoice
  where
    mvoiceToPVoice :: MVoice a -> PVoice a
    mvoiceToPVoice =
      map ( bimap voiceToRest voiceToPhrase
          . bimap (^.from unsafePairs) (^.from unsafePairs) )
       . groupDiff' (isJust . snd)
       . view pairs

    voiceToRest :: MVoice a -> Duration
    voiceToRest = sumOf (pairs.each._1) . fmap (\x -> assert (isNothing x) x)
    -- TODO just _duration

    voiceToPhrase :: MVoice a -> Phrase a
    voiceToPhrase = fmap fromJust

    pVoiceToMVoice :: (PVoice a) -> MVoice a
    pVoiceToMVoice = mconcat . fmap (either restToVoice phraseToVoice)

    restToVoice :: Duration -> MVoice a
    restToVoice d = stretch d $ pure Nothing

    phraseToVoice :: Phrase a -> MVoice a
    phraseToVoice = fmap Just


-- TODO failure
singleMVoice :: Prism (Score a) (Score b) (MVoice a) (MVoice b)
singleMVoice = iso scoreToVoice voiceToScore'
  where
    scoreToVoice :: {-Transformable a =>-} Score a -> MVoice a
    scoreToVoice = (^. voice) . fmap (^. note) . fmap throwTime . addRests .
      -- TODO
      List.sortBy (comparing (^._1))
      -- end TODO
      . (^. triples)
      where
        throwTime (t,d,x) = (d,x)
        addRests = concat . snd . List.mapAccumL g 0
          where
            g u (t, d, x)
              | u == t    = (t .+^ d, [(t, d, Just x)])
              | u <  t    = (t .+^ d, [(u, t .-. u, Nothing), (t, d, Just x)])
              | otherwise = error "singleMVoice: Strange prevTime"

    voiceToScore :: Voice a -> Score a
    voiceToScore = renderAlignedVoice . aligned 0 0

    voiceToScore' :: MVoice a -> Score a
    voiceToScore' = mcatMaybes . voiceToScore


-- foo :: HasPhrases' s a => s -> [TVoice a]
mapPhrasesWithPrevAndCurrentOnset :: HasPhrases s t a b => (Maybe Time -> Time -> Phrase a -> Phrase b) -> s -> t
mapPhrasesWithPrevAndCurrentOnset f = over (mvoices . mVoiceTVoice) (withPrevAndCurrentOnset f)

withPrevAndCurrentOnset :: (Maybe Time -> Time -> a -> b) -> Track a -> Track b
withPrevAndCurrentOnset f = over placeds (fmap (\(x,y,z) -> fmap (f (fmap placedOnset x) (placedOnset y)) y) . withPrevNext)
  where
    placedOnset :: Placed a -> Time
    placedOnset = view (from placed . _1)

mVoiceTVoice :: Lens (MVoice a) (MVoice b) (TVoice a) (TVoice b)
mVoiceTVoice = mVoicePVoice . pVoiceTVoice

pVoiceTVoice :: Lens (PVoice a) (PVoice b) (TVoice a) (TVoice b)
pVoiceTVoice = lens pVoiceToTVoice (flip tVoiceToPVoice)
  where
    pVoiceToTVoice :: PVoice a -> TVoice a
    pVoiceToTVoice x = mkTrack $ rights $ map (sequenceA) $ firsts (offsetPoints (0::Time)) (withDurationR x)

    -- TODO assert no overlapping
    tVoiceToPVoice :: TVoice a -> PVoice b -> PVoice a
    tVoiceToPVoice tv pv = set _rights newPhrases pv
      where
        newPhrases = toListOf traverse tv


_rights :: Lens [Either a b] [Either a c] [b] [c]
_rights = lens _rightsGet (flip _rightsSet)
  where
    _rightsGet :: [Either a b] -> [b]
    _rightsGet = rights

    _rightsSet :: [c] -> [Either a b] -> [Either a c]
    _rightsSet cs = sndMapAccumL f cs
      where
        f cs     (Left a)  = (cs, Left a)
        f (c:cs) (Right b) = (cs, Right c)
        f []     (Right _) = error "No more cs"

    sndMapAccumL f z = snd . List.mapAccumL f z

firsts :: ([a] -> [b]) -> [(a,c)] -> [(b,c)]
firsts f = uncurry zip . first f . unzipR

mkTrack :: [(Time, a)] -> Track a
mkTrack = view track . map (view placed)

withDurationR :: (Functor f, HasDuration a) => f a -> f (Duration, a)
withDurationR = fmap $ \x -> (_duration x, x)

-- TODO generalize and move
mapWithDuration :: HasDuration a => (Duration -> a -> b) -> a -> b
mapWithDuration = over dual withDurationL . uncurry
  where
    withDurationL :: (Contravariant f, HasDuration a) => f (Duration, a) -> f a
    withDurationL = contramap $ \x -> (_duration x, x)

    dual :: Iso (a -> b) (c -> d) (Op b a) (Op d c)
    dual = iso Op getOp

dursToVoice :: [Duration] -> Voice ()
dursToVoice = mconcat . map (\d -> stretch d $ return ())

{-
>>> print $ view (mVoiceTVoice) $ (fmap Just (dursToVoice [1,2,1]) <> return Nothing <> return (Just ()))


-}

-- |
-- Group contigous sequences matching/not-matching the predicate.
--
-- >>> groupDiff (== 0) [0,1,2,3,5,0,0,6,7]
-- [[0],[1,2,3,5],[0,0],[6,7]]
--
groupDiff :: (a -> Bool) -> [a] -> [[a]]
groupDiff p []     = []
groupDiff p (x:xs)
  | p x       = (x : List.takeWhile p         xs) : groupDiff p (List.dropWhile p         xs)
  | not (p x) = (x : List.takeWhile (not . p) xs) : groupDiff p (List.dropWhile (not . p) xs)

groupDiff' :: (a -> Bool) -> [a] -> [Either [a] [a]]
groupDiff' p []     = []
groupDiff' p (x:xs)
  | not (p x) = Left  (x : List.takeWhile (not . p) xs) : groupDiff' p (List.dropWhile (not . p) xs)
  | p x       = Right (x : List.takeWhile p         xs) : groupDiff' p (List.dropWhile p         xs)


-- JUNK