{-# 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, Edward Lilley 2012–2014
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Clefs and staff positions.
--
-------------------------------------------------------------------------------------

module Music.Pitch.Clef (
      -- * Clef representation
      ClefSymbol,
      ClefOctave,
      Clef,
      symbolName,
      symbolPitch,
      positionPitch,
      pitchPosition,
      -- ** Properties
      isModernClef,
      isHistoricalClef,
      isVoiceClef,

      -- * Standard clefs
      trebleClef,
      bassClef,
      sopranoClef,
      mezzoSopranoClef,
      altoClef,
      tenorClef,
      baritoneClef,
  ) where

import Data.Typeable

import Music.Pitch.StaffLines
import Music.Pitch.Common
import Music.Pitch.Literal

data ClefSymbol = GClef | CClef | FClef | PercClef | NeutralClef
    deriving (Eq, Ord, Show, Typeable)

type ClefOctave = Integer
type ClefLine   = StaffLines

type Clef = (ClefSymbol, ClefOctave, ClefLine)

symbolName :: ClefSymbol -> String
symbolName GClef = "G clef"
symbolName CClef = "C clef"
symbolName FClef = "F clef"
symbolName PercClef = "Percussion clef"
symbolName NeutralClef = "Neutral clef"

symbolPitch :: ClefSymbol -> Maybe Pitch
symbolPitch GClef = Just g
symbolPitch CClef = Just c
symbolPitch FClef = Just f
symbolPitch _     = Nothing

-- TODO consolidate with common
pitchPosition :: Clef -> Pitch -> Maybe StaffLines
pitchPosition (s,o,l) x = undefined
  where
    numbersPerOctave = 7
    referencePitch = symbolPitch s :: Maybe Pitch

positionPitch :: Clef -> StaffLines -> Maybe Pitch
positionPitch (s,o,l) x = fmap (upDiatonic relativePosition) referencePitch
  where
    numbersPerOctave = 7
    referencePitch = symbolPitch s :: Maybe Pitch
    relativePosition = fromIntegral $ (x - l) + fromIntegral (o*7)

-- TODO implement fully in Pitch.Common.Diatonic
upDiatonic :: Number -> Pitch -> Pitch
upDiatonic = error "Not implemented: upDiatonic"

{-
TODO

IsPitch instance?
  What to do with the non-standard pitches, i.e.
  we know what (g :: ClefSymbol) is, but what about (cs :: ClefSymbol)?
  I think 1) error or 2) default to g.

Map this to Pitch.Common
  Do we make this module 1) depend on Pitch.Common or 2) the other way around?
  If 1), do we need to separate G/C/F in ClefSymbol, maybe just put a single constructor for common pitch.
  In that case, maybe a function isStandardClefPitch (i.e. c/f/g) would be appropriate.
-}
trebleClef :: Clef
bassClef :: Clef
sopranoClef :: Clef
mezzoSopranoClef :: Clef
altoClef :: Clef
tenorClef :: Clef
baritoneClef :: Clef
trebleClef        = (GClef, -1 :: ClefOctave, -1 :: ClefLine)
bassClef          = (FClef, 1  :: ClefOctave, -1 :: ClefLine)
sopranoClef       = (CClef, 0  :: ClefOctave, -2 :: ClefLine)
mezzoSopranoClef  = (CClef, 0  :: ClefOctave, -1 :: ClefLine)
altoClef          = (CClef, 0  :: ClefOctave, 0  :: ClefLine)
tenorClef         = (CClef, 0  :: ClefOctave, 1  :: ClefLine)
baritoneClef      = (CClef, 0  :: ClefOctave, 2  :: ClefLine)

isModernClef :: Clef -> Bool
isModernClef x |x == trebleClef  = True
isModernClef x |x == bassClef    = True
isModernClef x |x == altoClef    = True
isModernClef x |x == tenorClef   = True
isModernClef x |otherwise        = False

isHistoricalClef :: Clef -> Bool
isHistoricalClef _ = False

-- | Is this a voice clef, i.e. a c clef on some staff.
isVoiceClef :: Clef -> Bool
isVoiceClef x |x == altoClef    = True
isVoiceClef x |x == tenorClef   = True
isVoiceClef x |otherwise        = False