{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImplicitParams #-}

-- | A `MusicGenerator` that uses Chaos functions.

module Generate.Chaos where

import Music
import Utils.Vec
import Generate.Generate
import Export
import Control.Monad.State hiding (state)

-- | Selectors for all `Generate.Generate.GenState` elements.
data Mapping n = Mapping { pcSel  :: Selector (ChaosState n) PitchClass
                         , octSel :: Selector (ChaosState n) Octave
                         , durSel :: Selector (ChaosState n) Duration
                         , itvSel :: Selector (ChaosState n) Interval
                         , dynSel :: Selector (ChaosState n) Dynamic
                         , artSel :: Selector (ChaosState n) Articulation
                         }

-- | Default `Mapping` that just grabs the first element from the list of
--   possible values.
defaultMapping :: Mapping n
defaultMapping = Mapping  { pcSel  = defaultChaosSelector
                          , octSel = defaultChaosSelector
                          , durSel = defaultChaosSelector
                          , itvSel = defaultChaosSelector
                          , dynSel = defaultChaosSelector
                          , artSel = defaultChaosSelector
                          }

-- | Default Chaos selector, (just grabs the first element from the list).
defaultChaosSelector :: Selector (ChaosState n) a
defaultChaosSelector s as = do
  return (snd (head as), s)

-- | Generates an `Entry` based on a `ChaosState` and `Selector`.
chaosEntry :: (Enum a, Bounded a) => ChaosState n -> Selector (ChaosState n) a -> Entry (ChaosState n) a
chaosEntry _ sel = Entry { values      = zip (repeat 1) [minBound ..]
                     , constraints = []
                     , selector    = sel
                     }

-- | Builds a `GenState` with a `ChaosState` based on a `ChaosState` and `Mapping`
chaosState :: ChaosState n -> Mapping n -> GenState (ChaosState n)
chaosState st m = GenState { state = st
                         , pc  = chaosEntry st (pcSel m)
                         , oct = chaosEntry st (octSel m)
                         , dur = Entry { values =
                                           zip (repeat 1) [1%1,1%2,1%4,1%8,1%16]
                                       , constraints = []
                                       , selector    = (durSel m)
                                       }
                         , itv = chaosEntry st (itvSel m)
                         , dyn = chaosEntry st (dynSel m)
                         , art = chaosEntry st (artSel m)
                         }

-- | Runs a generator on the chaos state.
runChaosGenerator :: ChaosState n -> Mapping n -> MusicGenerator (ChaosState n) a -> IO a
runChaosGenerator s m g = runGenerator' (chaosState s m) g

-- | Cleans the `MusicGenerator`
cleanChaos :: ChaosState n -> Mapping n -> MusicGenerator (ChaosState n) a -> MusicGenerator (ChaosState n) a
cleanChaos s m = modified (const $ chaosState s m)

-- | Generates music and plays it using Midi on device 0.
playChaosGen :: ToMusicCore a => ChaosState n -> Mapping n -> MusicGenerator (ChaosState n) (Music a) -> IO ()
playChaosGen s m gen = do
  music <- runChaosGenerator s m gen
  let ?midiConfig = defaultMIDIConfig
  playDev 0 music

-- | Builds a ChaosState from two Vectors of the same length. This constraint
--   is imposed since the number of variables should be equal to the number
--   of update functions.
buildChaos :: Vec n Double                   -- ^ Initial variable values
           -> Vec n (Vec n Double -> Double) -- ^ Functions that calculate next variable values
           -> ChaosState n
buildChaos vs fs = ChaosState { variables=vs , updateFunctions=fs}

-- | The default `ChaosState` that is used for Chaotic generation.
data ChaosState n =
  ChaosState { variables       :: Vec n Double
             , updateFunctions :: Vec n (Vec n Double -> Double)
             }

-- | The `ChaosState wrapped in a `StateT` monad.`
type ChaosGenerator n = StateT (ChaosState n) IO

-- | Calculates the next iteration of values for the `ChaosState`.
genNextIteration :: ChaosGenerator n [Double]
genNextIteration = do
    s <- get
    let vs = variables s
    let fs = updateFunctions s
    let newVs = fmap (\f -> f vs) fs
    put (s { variables = newVs })
    return $ list newVs