{-# LANGUAGE RecordWildCards, NoImplicitPrelude #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Mezzo.Render.Score
-- Description :  Score building
-- Copyright   :  (c) Dima Szamozvancev
-- License     :  MIT
--
-- Maintainer  :  ds709@cam.ac.uk
-- Stability   :  experimental
-- Portability :  portable
--
-- Combinators for building scores: 'Music' values with global composition
-- attributes such as tempo or key signature.
--
-----------------------------------------------------------------------------

module Mezzo.Render.Score
    ( -- * Scores and attributes
      Attributes (..)
    , defAttributes
    , getTimeSig
    , getKeySig
      -- * Score builders
    , score
    , section
    , setTempo
    , setTimeSig
    , setKeySig
    , setRuleSet
      -- * Rule sets
    , free
    , classical
    , strict
    )
    where

import Mezzo.Model
import Mezzo.Compose.Harmony
import Mezzo.Compose.Builder

import Codec.Midi hiding (key, Key)
import qualified Codec.Midi as CM (key, Key)
import qualified GHC.TypeLits as GT
import Prelude hiding (min)

-------------------------------------------------------------------------------
-- Attributes
-------------------------------------------------------------------------------

-- | Datatype containing MIDI attributes of a Mezzo composition.
data Attributes t k r =
    (Primitive t, Primitive k, ScoreAtt t, ScoreAtt k)
    => Attributes
    { title :: String               -- ^ The title of the composition.
    , tempo :: Tempo                -- ^ The tempo of the composition in BPM.
    , timeSignature :: TimeSig t    -- ^ The time signature of the composition.
    , keySignature :: KeyS k        -- ^ The key signature of the composition.
    , ruleSet :: r
    }

-- | Default attributes: "Composition" in C major in common time, with tempo 120 BPM.
defAttributes :: Attributes 4 (Key C Natural MajorMode) Classical
defAttributes = Attributes
    { title = "Composition"
    , tempo = 120
    , timeSignature = quadruple
    , keySignature = c_maj
    , ruleSet = classical
    }

-------------------------------------------------------------------------------
-- Builders
-------------------------------------------------------------------------------

-- Score attribute specifier: uses the default attributes.
score :: Spec (Attributes 4 (Key C Natural MajorMode) Classical)
score = spec defAttributes

-- | Sets the title of the composition.
section :: AMut String (Attributes t k r)
section atts titl = spec (atts {title = titl})

-- | Sets the tempo of the composition.
setTempo :: AMut Tempo (Attributes t k r)
setTempo atts temp = spec (atts {tempo = temp})

-- | Sets the time signature of the composition.
setTimeSig :: (Primitive t', ScoreAtt t') => AConv (TimeSig t') (Attributes t k r) (Attributes t' k r)
setTimeSig Attributes{..} ts = spec (Attributes title tempo ts keySignature ruleSet)

-- | Sets the key signature of the composition.
setKeySig :: (Primitive k', ScoreAtt k') => AConv (KeyS k') (Attributes t k r) (Attributes t k' r)
setKeySig Attributes{..} ks = spec (Attributes title tempo timeSignature ks ruleSet)

-- | Sets the key signature of the composition.
setRuleSet :: AConv r' (Attributes t k r) (Attributes t k r')
setRuleSet Attributes{..} rs = spec (Attributes title tempo timeSignature keySignature rs)


-- | Get the time signature MIDI message.
getTimeSig :: Attributes t k r -> Message
getTimeSig Attributes{timeSignature = t} = getAtt t

-- | Get the key signature MIDI message.
getKeySig :: Attributes t k r -> Message
getKeySig Attributes{keySignature = k} = getAtt k

-------------------------------------------------------------------------------
-- Rule sets
-------------------------------------------------------------------------------

-- | No enforced rules.
free :: Free
free = Free

-- | Classical rules.
classical :: Classical
classical = Classical

-- | Strict rules
strict :: Strict
strict = Strict



-- | Class for types that can be converted into score attribute MIDI messages.
class ScoreAtt a where
    -- | Get the MIDI message corresponding to a type-level attribute.
    getAtt :: proxy a -> Message

instance ScoreAtt 2 where getAtt t = TimeSignature 2 2 24 8
instance ScoreAtt 3 where getAtt t = TimeSignature 3 2 24 8
instance ScoreAtt 4 where getAtt t = TimeSignature 4 2 24 8

instance ScoreAtt (Key C Flat    MajorMode) where getAtt k = KeySignature (-7) 0
instance ScoreAtt (Key G Flat    MajorMode) where getAtt k = KeySignature (-6) 0
instance ScoreAtt (Key D Flat    MajorMode) where getAtt k = KeySignature (-5) 0
instance ScoreAtt (Key A Flat    MajorMode) where getAtt k = KeySignature (-4) 0
instance ScoreAtt (Key E Flat    MajorMode) where getAtt k = KeySignature (-3) 0
instance ScoreAtt (Key B Flat    MajorMode) where getAtt k = KeySignature (-2) 0
instance ScoreAtt (Key F Natural MajorMode) where getAtt k = KeySignature (-1) 0
instance ScoreAtt (Key C Natural MajorMode) where getAtt k = KeySignature  0 0
instance ScoreAtt (Key G Natural MajorMode) where getAtt k = KeySignature  1 0
instance ScoreAtt (Key D Natural MajorMode) where getAtt k = KeySignature  2 0
instance ScoreAtt (Key A Natural MajorMode) where getAtt k = KeySignature  3 0
instance ScoreAtt (Key E Natural MajorMode) where getAtt k = KeySignature  4 0
instance ScoreAtt (Key B Natural MajorMode) where getAtt k = KeySignature  5 0
instance ScoreAtt (Key F Sharp   MajorMode) where getAtt k = KeySignature  6 0
instance ScoreAtt (Key C Sharp   MajorMode) where getAtt k = KeySignature  7 0

instance ScoreAtt (Key A Flat    MinorMode) where getAtt k = KeySignature (-7) 1
instance ScoreAtt (Key E Flat    MinorMode) where getAtt k = KeySignature (-6) 1
instance ScoreAtt (Key B Flat    MinorMode) where getAtt k = KeySignature (-5) 1
instance ScoreAtt (Key F Natural MinorMode) where getAtt k = KeySignature (-4) 1
instance ScoreAtt (Key C Natural MinorMode) where getAtt k = KeySignature (-3) 1
instance ScoreAtt (Key G Natural MinorMode) where getAtt k = KeySignature (-2) 1
instance ScoreAtt (Key D Natural MinorMode) where getAtt k = KeySignature (-1) 1
instance ScoreAtt (Key A Natural MinorMode) where getAtt k = KeySignature  0 1
instance ScoreAtt (Key E Natural MinorMode) where getAtt k = KeySignature  1 1
instance ScoreAtt (Key B Natural MinorMode) where getAtt k = KeySignature  2 1
instance ScoreAtt (Key F Sharp   MinorMode) where getAtt k = KeySignature  3 1
instance ScoreAtt (Key C Sharp   MinorMode) where getAtt k = KeySignature  4 1
instance ScoreAtt (Key G Sharp   MinorMode) where getAtt k = KeySignature  5 1
instance ScoreAtt (Key D Sharp   MinorMode) where getAtt k = KeySignature  6 1
instance ScoreAtt (Key A Sharp   MinorMode) where getAtt k = KeySignature  7 1

instance {-# OVERLAPPABLE #-} GT.TypeError (GT.Text "The key signature is invalid.")
    => ScoreAtt (Key pc acc mode) where
    getAtt = undefined